rasa-pro 3.14.0rc4__py3-none-any.whl → 3.14.2__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/agents/agent_manager.py +7 -5
- rasa/agents/protocol/a2a/a2a_agent.py +13 -11
- rasa/agents/protocol/mcp/mcp_base_agent.py +49 -11
- rasa/agents/validation.py +4 -2
- rasa/builder/copilot/copilot_templated_message_provider.py +1 -1
- rasa/builder/validation_service.py +4 -0
- rasa/cli/arguments/data.py +9 -0
- rasa/cli/data.py +72 -6
- rasa/cli/interactive.py +3 -0
- rasa/cli/llm_fine_tuning.py +1 -0
- rasa/cli/project_templates/defaults.py +1 -0
- rasa/cli/validation/bot_config.py +2 -0
- rasa/constants.py +2 -1
- rasa/core/actions/action_clean_stack.py +32 -0
- rasa/core/actions/action_exceptions.py +1 -1
- rasa/core/actions/constants.py +4 -0
- rasa/core/actions/custom_action_executor.py +70 -12
- rasa/core/actions/grpc_custom_action_executor.py +41 -2
- rasa/core/actions/http_custom_action_executor.py +49 -25
- rasa/core/agent.py +4 -1
- rasa/core/available_agents.py +1 -1
- rasa/core/channels/voice_stream/browser_audio.py +3 -3
- rasa/core/channels/voice_stream/voice_channel.py +27 -17
- rasa/core/config/credentials.py +3 -3
- rasa/core/exceptions.py +1 -1
- rasa/core/featurizers/tracker_featurizers.py +3 -2
- rasa/core/persistor.py +7 -7
- rasa/core/policies/flows/agent_executor.py +84 -4
- rasa/core/policies/flows/flow_exceptions.py +5 -2
- rasa/core/policies/flows/flow_executor.py +52 -31
- rasa/core/policies/flows/mcp_tool_executor.py +7 -1
- rasa/core/policies/rule_policy.py +1 -1
- rasa/core/run.py +21 -5
- rasa/dialogue_understanding/commands/cancel_flow_command.py +1 -1
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +6 -3
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +15 -7
- rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +15 -8
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +1 -1
- rasa/dialogue_understanding/processor/command_processor.py +13 -7
- rasa/e2e_test/e2e_config.py +4 -3
- rasa/engine/recipes/default_components.py +16 -6
- rasa/graph_components/validators/default_recipe_validator.py +10 -4
- rasa/model_manager/runner_service.py +1 -1
- rasa/nlu/classifiers/diet_classifier.py +2 -0
- rasa/privacy/privacy_config.py +1 -1
- rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +4 -7
- rasa/shared/core/flows/flow.py +8 -2
- rasa/shared/core/slots.py +55 -24
- rasa/shared/core/training_data/story_reader/story_reader.py +1 -1
- rasa/shared/exceptions.py +23 -2
- rasa/shared/providers/_configs/azure_openai_client_config.py +4 -5
- rasa/shared/providers/_configs/default_litellm_client_config.py +4 -4
- rasa/shared/providers/_configs/litellm_router_client_config.py +3 -2
- rasa/shared/providers/_configs/openai_client_config.py +5 -7
- rasa/shared/providers/_configs/rasa_llm_client_config.py +4 -4
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +4 -4
- rasa/shared/providers/llm/_base_litellm_client.py +42 -14
- rasa/shared/providers/llm/litellm_router_llm_client.py +40 -17
- rasa/shared/providers/llm/self_hosted_llm_client.py +34 -32
- rasa/shared/utils/common.py +9 -1
- rasa/shared/utils/configs.py +5 -8
- rasa/shared/utils/llm.py +21 -4
- rasa/shared/utils/mcp/server_connection.py +7 -4
- rasa/studio/download.py +3 -0
- rasa/studio/prompts.py +1 -0
- rasa/studio/upload.py +4 -0
- rasa/utils/common.py +9 -0
- rasa/utils/endpoints.py +6 -0
- rasa/utils/installation_utils.py +111 -0
- rasa/utils/log_utils.py +20 -1
- rasa/utils/tensorflow/callback.py +2 -0
- rasa/utils/tensorflow/models.py +3 -0
- rasa/utils/train_utils.py +2 -0
- rasa/version.py +1 -1
- {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.14.2.dist-info}/METADATA +3 -3
- {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.14.2.dist-info}/RECORD +79 -78
- {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.14.2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.14.2.dist-info}/WHEEL +0 -0
- {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.14.2.dist-info}/entry_points.txt +0 -0
|
@@ -2,7 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
import logging
|
|
5
|
-
from
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Text
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
6
9
|
|
|
7
10
|
import rasa
|
|
8
11
|
from rasa.core.actions.action_exceptions import DomainNotFound
|
|
@@ -19,6 +22,23 @@ if TYPE_CHECKING:
|
|
|
19
22
|
logger = logging.getLogger(__name__)
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
class ActionResultType(Enum):
|
|
26
|
+
SUCCESS = "success"
|
|
27
|
+
RETRY_WITH_DOMAIN = "retry_with_domain"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ActionResult(BaseModel):
|
|
31
|
+
"""Result of custom action execution.
|
|
32
|
+
|
|
33
|
+
This is used to avoid raising exceptions for expected conditions
|
|
34
|
+
like missing domain (449 status code), which would otherwise be
|
|
35
|
+
captured by tracing as errors.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
result_type: ActionResultType
|
|
39
|
+
response: Optional[Dict[Text, Any]] = None
|
|
40
|
+
|
|
41
|
+
|
|
22
42
|
class CustomActionExecutor(abc.ABC):
|
|
23
43
|
"""Interface for custom action executors.
|
|
24
44
|
|
|
@@ -45,6 +65,34 @@ class CustomActionExecutor(abc.ABC):
|
|
|
45
65
|
"""
|
|
46
66
|
pass
|
|
47
67
|
|
|
68
|
+
async def run_with_result(
|
|
69
|
+
self,
|
|
70
|
+
tracker: "DialogueStateTracker",
|
|
71
|
+
domain: "Domain",
|
|
72
|
+
include_domain: bool = False,
|
|
73
|
+
) -> ActionResult:
|
|
74
|
+
"""Executes the custom action and returns a result.
|
|
75
|
+
|
|
76
|
+
This method is used to avoid raising exceptions for expected conditions
|
|
77
|
+
like missing domain, which would otherwise be captured by tracing as errors.
|
|
78
|
+
|
|
79
|
+
By default, this method calls the run method and wraps the response
|
|
80
|
+
for backward compatibility.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
tracker: The current state of the dialogue.
|
|
84
|
+
domain: The domain object containing domain-specific information.
|
|
85
|
+
include_domain: If True, the domain is included in the request.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
ActionResult containing the response and result type.
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
response = await self.run(tracker, domain, include_domain)
|
|
92
|
+
return ActionResult(result_type=ActionResultType.SUCCESS, response=response)
|
|
93
|
+
except DomainNotFound:
|
|
94
|
+
return ActionResult(result_type=ActionResultType.RETRY_WITH_DOMAIN)
|
|
95
|
+
|
|
48
96
|
|
|
49
97
|
class NoEndpointCustomActionExecutor(CustomActionExecutor):
|
|
50
98
|
"""Implementation of a custom action executor when endpoint is not set.
|
|
@@ -163,13 +211,13 @@ class RetryCustomActionExecutor(CustomActionExecutor):
|
|
|
163
211
|
domain: "Domain",
|
|
164
212
|
include_domain: bool = False,
|
|
165
213
|
) -> Dict[Text, Any]:
|
|
166
|
-
"""Runs the wrapped custom action executor.
|
|
214
|
+
"""Runs the wrapped custom action executor with retry logic.
|
|
167
215
|
|
|
168
216
|
First request to the action server is made with/without the domain
|
|
169
217
|
as specified by the `include_domain` parameter.
|
|
170
218
|
|
|
171
|
-
If the action server responds with a
|
|
172
|
-
|
|
219
|
+
If the action server responds with a missing domain indication,
|
|
220
|
+
retries the request with the domain included.
|
|
173
221
|
|
|
174
222
|
Args:
|
|
175
223
|
tracker: The current state of the dialogue.
|
|
@@ -178,14 +226,24 @@ class RetryCustomActionExecutor(CustomActionExecutor):
|
|
|
178
226
|
|
|
179
227
|
Returns:
|
|
180
228
|
The response from the execution of the custom action.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
DomainNotFound: If the action server still requires domain after retry.
|
|
181
232
|
"""
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
233
|
+
result = await self._custom_action_executor.run_with_result(
|
|
234
|
+
tracker,
|
|
235
|
+
domain,
|
|
236
|
+
include_domain=include_domain,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if result.result_type == ActionResultType.RETRY_WITH_DOMAIN:
|
|
240
|
+
# Retry with domain included
|
|
241
|
+
result = await self._custom_action_executor.run_with_result(
|
|
190
242
|
tracker, domain, include_domain=True
|
|
191
243
|
)
|
|
244
|
+
|
|
245
|
+
# If still missing domain after retry, raise error
|
|
246
|
+
if result.result_type == ActionResultType.RETRY_WITH_DOMAIN:
|
|
247
|
+
raise DomainNotFound()
|
|
248
|
+
|
|
249
|
+
return result.response if result.response is not None else {}
|
|
@@ -11,6 +11,8 @@ from rasa_sdk.grpc_py import action_webhook_pb2, action_webhook_pb2_grpc
|
|
|
11
11
|
from rasa.core.actions.action_exceptions import DomainNotFound
|
|
12
12
|
from rasa.core.actions.constants import SSL_CLIENT_CERT_FIELD, SSL_CLIENT_KEY_FIELD
|
|
13
13
|
from rasa.core.actions.custom_action_executor import (
|
|
14
|
+
ActionResult,
|
|
15
|
+
ActionResultType,
|
|
14
16
|
CustomActionExecutor,
|
|
15
17
|
CustomActionRequestWriter,
|
|
16
18
|
)
|
|
@@ -101,13 +103,51 @@ class GRPCCustomActionExecutor(CustomActionExecutor):
|
|
|
101
103
|
|
|
102
104
|
Returns:
|
|
103
105
|
Response from the action server.
|
|
106
|
+
Returns empty dict if domain is missing.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
RasaException: If an error occurs while making the gRPC request
|
|
110
|
+
(other than missing domain).
|
|
104
111
|
"""
|
|
112
|
+
result = await self.run_with_result(tracker, domain, include_domain)
|
|
113
|
+
|
|
114
|
+
# Return empty dict for retry cases to avoid raising exceptions
|
|
115
|
+
# RetryCustomActionExecutor will handle the retry logic
|
|
116
|
+
if result.result_type == ActionResultType.RETRY_WITH_DOMAIN:
|
|
117
|
+
return {}
|
|
118
|
+
|
|
119
|
+
return result.response if result.response is not None else {}
|
|
120
|
+
|
|
121
|
+
async def run_with_result(
|
|
122
|
+
self,
|
|
123
|
+
tracker: "DialogueStateTracker",
|
|
124
|
+
domain: "Domain",
|
|
125
|
+
include_domain: bool = False,
|
|
126
|
+
) -> ActionResult:
|
|
127
|
+
"""Execute the custom action and return an ActionResult.
|
|
128
|
+
|
|
129
|
+
This method avoids raising DomainNotFound exception for missing domain,
|
|
130
|
+
instead returning an ActionResult with RETRY_WITH_DOMAIN type.
|
|
131
|
+
This prevents tracing from capturing this expected condition as an error.
|
|
105
132
|
|
|
133
|
+
Args:
|
|
134
|
+
tracker: Tracker for the current conversation.
|
|
135
|
+
domain: Domain of the assistant.
|
|
136
|
+
include_domain: If True, the domain is included in the request.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
ActionResult containing the response and result type.
|
|
140
|
+
"""
|
|
106
141
|
request = self._create_payload(
|
|
107
142
|
tracker=tracker, domain=domain, include_domain=include_domain
|
|
108
143
|
)
|
|
109
144
|
|
|
110
|
-
|
|
145
|
+
try:
|
|
146
|
+
response = self._request(request)
|
|
147
|
+
return ActionResult(result_type=ActionResultType.SUCCESS, response=response)
|
|
148
|
+
except DomainNotFound:
|
|
149
|
+
# Return retry result instead of raising DomainNotFound
|
|
150
|
+
return ActionResult(result_type=ActionResultType.RETRY_WITH_DOMAIN)
|
|
111
151
|
|
|
112
152
|
def _request(
|
|
113
153
|
self,
|
|
@@ -121,7 +161,6 @@ class GRPCCustomActionExecutor(CustomActionExecutor):
|
|
|
121
161
|
Returns:
|
|
122
162
|
Response from the action server.
|
|
123
163
|
"""
|
|
124
|
-
|
|
125
164
|
client = self._create_grpc_client()
|
|
126
165
|
metadata = self._build_metadata()
|
|
127
166
|
try:
|
|
@@ -4,8 +4,11 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
|
4
4
|
|
|
5
5
|
import aiohttp
|
|
6
6
|
|
|
7
|
-
from rasa.core.actions.action_exceptions import ActionExecutionRejection
|
|
7
|
+
from rasa.core.actions.action_exceptions import ActionExecutionRejection
|
|
8
|
+
from rasa.core.actions.constants import MISSING_DOMAIN_MARKER
|
|
8
9
|
from rasa.core.actions.custom_action_executor import (
|
|
10
|
+
ActionResult,
|
|
11
|
+
ActionResultType,
|
|
9
12
|
CustomActionExecutor,
|
|
10
13
|
CustomActionRequestWriter,
|
|
11
14
|
)
|
|
@@ -18,12 +21,12 @@ from rasa.shared.core.domain import Domain
|
|
|
18
21
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
19
22
|
from rasa.shared.exceptions import RasaException
|
|
20
23
|
from rasa.utils.common import get_bool_env_variable
|
|
24
|
+
from rasa.utils.endpoints import ClientResponseError, EndpointConfig
|
|
21
25
|
|
|
22
26
|
if TYPE_CHECKING:
|
|
23
27
|
from rasa.shared.core.domain import Domain
|
|
24
28
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
25
29
|
|
|
26
|
-
from rasa.utils.endpoints import ClientResponseError, EndpointConfig
|
|
27
30
|
|
|
28
31
|
logger = logging.getLogger(__name__)
|
|
29
32
|
|
|
@@ -62,9 +65,40 @@ class HTTPCustomActionExecutor(CustomActionExecutor):
|
|
|
62
65
|
|
|
63
66
|
Returns:
|
|
64
67
|
A dictionary containing the response from the custom action endpoint.
|
|
68
|
+
Returns empty dict if domain is missing (449 status).
|
|
65
69
|
|
|
66
70
|
Raises:
|
|
67
|
-
RasaException: If an error occurs while making the HTTP request
|
|
71
|
+
RasaException: If an error occurs while making the HTTP request
|
|
72
|
+
(other than missing domain).
|
|
73
|
+
"""
|
|
74
|
+
result = await self.run_with_result(tracker, domain, include_domain)
|
|
75
|
+
|
|
76
|
+
# Return empty dict for retry cases to avoid raising exceptions
|
|
77
|
+
# RetryCustomActionExecutor will handle the retry logic
|
|
78
|
+
if result.result_type == ActionResultType.RETRY_WITH_DOMAIN:
|
|
79
|
+
return {}
|
|
80
|
+
|
|
81
|
+
return result.response if result.response is not None else {}
|
|
82
|
+
|
|
83
|
+
async def run_with_result(
|
|
84
|
+
self,
|
|
85
|
+
tracker: "DialogueStateTracker",
|
|
86
|
+
domain: Optional["Domain"] = None,
|
|
87
|
+
include_domain: bool = False,
|
|
88
|
+
) -> ActionResult:
|
|
89
|
+
"""Execute the custom action and return an ActionResult.
|
|
90
|
+
|
|
91
|
+
This method avoids raising DomainNotFound exception for 449 status code,
|
|
92
|
+
instead returning an ActionResult with RETRY_WITH_DOMAIN type.
|
|
93
|
+
This prevents tracing from capturing this expected condition as an error.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
tracker: The current state of the dialogue.
|
|
97
|
+
domain: The domain object containing domain-specific information.
|
|
98
|
+
include_domain: If True, the domain is included in the request.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
ActionResult containing the response and result type.
|
|
68
102
|
"""
|
|
69
103
|
from rasa.core.actions.action import RemoteActionJSONValidator
|
|
70
104
|
|
|
@@ -77,14 +111,23 @@ class HTTPCustomActionExecutor(CustomActionExecutor):
|
|
|
77
111
|
tracker=tracker, domain=domain, include_domain=include_domain
|
|
78
112
|
)
|
|
79
113
|
|
|
80
|
-
|
|
114
|
+
assert self.action_endpoint is not None
|
|
115
|
+
response = await self.action_endpoint.request(
|
|
116
|
+
json=json_body,
|
|
117
|
+
method="post",
|
|
118
|
+
timeout=DEFAULT_REQUEST_TIMEOUT,
|
|
119
|
+
compress=self.should_compress,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Check if we got the special marker for 449 status (missing domain)
|
|
123
|
+
if isinstance(response, dict) and response.get(MISSING_DOMAIN_MARKER):
|
|
124
|
+
return ActionResult(result_type=ActionResultType.RETRY_WITH_DOMAIN)
|
|
81
125
|
|
|
82
126
|
if response is None:
|
|
83
127
|
response = {}
|
|
84
128
|
|
|
85
129
|
RemoteActionJSONValidator.validate(response)
|
|
86
|
-
|
|
87
|
-
return response
|
|
130
|
+
return ActionResult(result_type=ActionResultType.SUCCESS, response=response)
|
|
88
131
|
|
|
89
132
|
except ClientResponseError as e:
|
|
90
133
|
if e.status == 400:
|
|
@@ -131,22 +174,3 @@ class HTTPCustomActionExecutor(CustomActionExecutor):
|
|
|
131
174
|
"and returns a 200 once the action is executed. "
|
|
132
175
|
"Error: {}".format(self.action_name, status, e)
|
|
133
176
|
)
|
|
134
|
-
|
|
135
|
-
async def _perform_request_with_retries(
|
|
136
|
-
self,
|
|
137
|
-
json_body: Dict[str, Any],
|
|
138
|
-
) -> Any:
|
|
139
|
-
"""Attempts to perform the request with retries if necessary."""
|
|
140
|
-
assert self.action_endpoint is not None
|
|
141
|
-
try:
|
|
142
|
-
return await self.action_endpoint.request(
|
|
143
|
-
json=json_body,
|
|
144
|
-
method="post",
|
|
145
|
-
timeout=DEFAULT_REQUEST_TIMEOUT,
|
|
146
|
-
compress=self.should_compress,
|
|
147
|
-
)
|
|
148
|
-
except ClientResponseError as e:
|
|
149
|
-
# Repeat the request because Domain was not in the payload
|
|
150
|
-
if e.status == 449:
|
|
151
|
-
raise DomainNotFound()
|
|
152
|
-
raise e
|
rasa/core/agent.py
CHANGED
|
@@ -297,7 +297,10 @@ async def load_agent(
|
|
|
297
297
|
return agent
|
|
298
298
|
|
|
299
299
|
except AgentInitializationException as e:
|
|
300
|
-
|
|
300
|
+
if e.suppress_stack_trace:
|
|
301
|
+
raise e from None
|
|
302
|
+
else:
|
|
303
|
+
raise e
|
|
301
304
|
except Exception as e:
|
|
302
305
|
logger.error(f"Could not load model due to {e}.", exc_info=True)
|
|
303
306
|
return agent
|
rasa/core/available_agents.py
CHANGED
|
@@ -114,7 +114,7 @@ class AvailableAgents:
|
|
|
114
114
|
else:
|
|
115
115
|
# We are using the default folder, it may not be created yet
|
|
116
116
|
# Init with an empty agents in this case
|
|
117
|
-
structlogger.
|
|
117
|
+
structlogger.debug(
|
|
118
118
|
f"Default agents config folder '{sub_agents_folder}' does not "
|
|
119
119
|
f"exist. Agent configurations won't be loaded."
|
|
120
120
|
)
|
|
@@ -90,13 +90,13 @@ class BrowserAudioInputChannel(VoiceInputChannel):
|
|
|
90
90
|
self._wav_file: Optional[wave.Wave_write] = None
|
|
91
91
|
|
|
92
92
|
def _start_recording(self, call_id: str, user_id: str) -> None:
|
|
93
|
+
if not self._recording_enabled:
|
|
94
|
+
return
|
|
95
|
+
|
|
93
96
|
os.makedirs("recordings", exist_ok=True)
|
|
94
97
|
filename = f"{user_id}_{call_id}.wav"
|
|
95
98
|
file_path = os.path.join("recordings", filename)
|
|
96
99
|
|
|
97
|
-
if not self._recording_enabled:
|
|
98
|
-
return
|
|
99
|
-
|
|
100
100
|
self._wav_file = wave.open(file_path, "wb")
|
|
101
101
|
self._wav_file.setnchannels(1) # Mono audio
|
|
102
102
|
self._wav_file.setsampwidth(4) # 32-bit audio (4 bytes)
|
|
@@ -192,6 +192,8 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
192
192
|
async def send_start_marker(self, recipient_id: str) -> None:
|
|
193
193
|
"""Send a marker message before the first audio chunk."""
|
|
194
194
|
# Default implementation uses the generic marker message
|
|
195
|
+
call_state.is_bot_speaking = True
|
|
196
|
+
VoiceInputChannel._cancel_silence_timeout_watcher()
|
|
195
197
|
await self.send_marker_message(recipient_id)
|
|
196
198
|
|
|
197
199
|
async def send_intermediate_marker(self, recipient_id: str) -> None:
|
|
@@ -268,11 +270,6 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
268
270
|
# Track TTS start time
|
|
269
271
|
call_state.tts_start_time = time.time()
|
|
270
272
|
|
|
271
|
-
cached_audio_bytes = self.tts_cache.get(text)
|
|
272
|
-
collected_audio_bytes = RasaAudioBytes(b"")
|
|
273
|
-
seconds_marker = -1
|
|
274
|
-
last_sent_offset = 0
|
|
275
|
-
first_audio_sent = False
|
|
276
273
|
logger.debug("voice_channel.sending_audio", text=text)
|
|
277
274
|
|
|
278
275
|
# Send start marker before first chunk
|
|
@@ -285,17 +282,12 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
285
282
|
allow_interruptions = kwargs.get("allow_interruptions", True)
|
|
286
283
|
call_state.channel_data["allow_interruptions"] = allow_interruptions
|
|
287
284
|
|
|
288
|
-
|
|
289
|
-
audio_stream = self.chunk_audio(cached_audio_bytes)
|
|
290
|
-
else:
|
|
291
|
-
# Todo: make kwargs compatible with engine config
|
|
292
|
-
synth_config = self.tts_engine.config.__class__.from_dict({})
|
|
293
|
-
try:
|
|
294
|
-
audio_stream = self.tts_engine.synthesize(text, synth_config)
|
|
295
|
-
except TTSError:
|
|
296
|
-
# TODO: add message that works without tts, e.g. loading from disc
|
|
297
|
-
audio_stream = self.chunk_audio(generate_silence())
|
|
285
|
+
audio_stream = await self._create_audio_stream(text)
|
|
298
286
|
|
|
287
|
+
collected_audio_bytes = RasaAudioBytes(b"")
|
|
288
|
+
last_sent_offset = 0
|
|
289
|
+
first_audio_sent = False
|
|
290
|
+
seconds_marker = -1
|
|
299
291
|
async for audio_bytes in audio_stream:
|
|
300
292
|
collected_audio_bytes = RasaAudioBytes(collected_audio_bytes + audio_bytes)
|
|
301
293
|
|
|
@@ -315,6 +307,8 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
315
307
|
await self.send_audio_bytes(recipient_id, new_bytes)
|
|
316
308
|
last_sent_offset = len(collected_audio_bytes)
|
|
317
309
|
|
|
310
|
+
# seconds of audio rounded down to floor number
|
|
311
|
+
# e.g 7 // 2 = 3
|
|
318
312
|
full_seconds_of_audio = len(collected_audio_bytes) // HERTZ
|
|
319
313
|
if full_seconds_of_audio > seconds_marker:
|
|
320
314
|
await self.send_intermediate_marker(recipient_id)
|
|
@@ -348,7 +342,7 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
348
342
|
pass
|
|
349
343
|
call_state.latest_bot_audio_id = self.latest_message_id
|
|
350
344
|
|
|
351
|
-
if not
|
|
345
|
+
if not self.tts_cache.get(text):
|
|
352
346
|
self.tts_cache.put(text, collected_audio_bytes)
|
|
353
347
|
|
|
354
348
|
async def send_audio_bytes(
|
|
@@ -373,6 +367,22 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
373
367
|
async def hangup(self, recipient_id: str, **kwargs: Any) -> None:
|
|
374
368
|
call_state.should_hangup = True
|
|
375
369
|
|
|
370
|
+
async def _create_audio_stream(self, text: str) -> AsyncIterator[RasaAudioBytes]:
|
|
371
|
+
cached_audio_bytes = self.tts_cache.get(text)
|
|
372
|
+
|
|
373
|
+
if cached_audio_bytes:
|
|
374
|
+
audio_stream = self.chunk_audio(cached_audio_bytes)
|
|
375
|
+
else:
|
|
376
|
+
# Todo: make kwargs compatible with engine config
|
|
377
|
+
synth_config = self.tts_engine.config.__class__.from_dict({})
|
|
378
|
+
try:
|
|
379
|
+
audio_stream = self.tts_engine.synthesize(text, synth_config)
|
|
380
|
+
except TTSError:
|
|
381
|
+
# TODO: add message that works without tts, e.g. loading from disc
|
|
382
|
+
audio_stream = self.chunk_audio(generate_silence())
|
|
383
|
+
|
|
384
|
+
return audio_stream
|
|
385
|
+
|
|
376
386
|
|
|
377
387
|
class VoiceInputChannel(InputChannel):
|
|
378
388
|
# All children of this class require a voice license to be used.
|
|
@@ -555,7 +565,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
555
565
|
# relevant when the bot speaks multiple messages in one turn
|
|
556
566
|
self._cancel_silence_timeout_watcher()
|
|
557
567
|
|
|
558
|
-
#
|
|
568
|
+
# bot just stopped speaking, starting a watcher for silence timeout
|
|
559
569
|
if was_bot_speaking_before and not is_bot_speaking_after:
|
|
560
570
|
logger.debug("voice_channel.bot_stopped_speaking")
|
|
561
571
|
self._cancel_silence_timeout_watcher()
|
rasa/core/config/credentials.py
CHANGED
|
@@ -5,11 +5,11 @@ from typing import Any, Dict
|
|
|
5
5
|
|
|
6
6
|
from rasa.shared.utils.yaml import read_config_file
|
|
7
7
|
|
|
8
|
+
ChannelsType = Dict[str, Dict[str, Any]]
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
class CredentialsConfig:
|
|
10
|
-
def __init__(
|
|
11
|
-
self, channels: Dict[str, Dict[str, Any]], config_file_path: Path
|
|
12
|
-
) -> None:
|
|
12
|
+
def __init__(self, channels: ChannelsType, config_file_path: Path) -> None:
|
|
13
13
|
self.channels = channels
|
|
14
14
|
self.config_file_path = config_file_path
|
|
15
15
|
|
rasa/core/exceptions.py
CHANGED
|
@@ -14,7 +14,7 @@ class AgentNotReady(RasaCoreException):
|
|
|
14
14
|
def __init__(self, message: Text) -> None:
|
|
15
15
|
"""Initialize message attribute."""
|
|
16
16
|
self.message = message
|
|
17
|
-
super(AgentNotReady, self).__init__()
|
|
17
|
+
super(AgentNotReady, self).__init__(message)
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class ChannelConfigError(RasaCoreException):
|
|
@@ -58,7 +58,7 @@ class InvalidStory(RasaException):
|
|
|
58
58
|
message: a custom exception message.
|
|
59
59
|
"""
|
|
60
60
|
self.message = message
|
|
61
|
-
super(InvalidStory, self).__init__()
|
|
61
|
+
super(InvalidStory, self).__init__(message)
|
|
62
62
|
|
|
63
63
|
def __str__(self) -> Text:
|
|
64
64
|
return self.message
|
|
@@ -174,6 +174,7 @@ class TrackerFeaturizer:
|
|
|
174
174
|
|
|
175
175
|
Args:
|
|
176
176
|
trackers_as_actions: A list of tracker labels.
|
|
177
|
+
domain: The domain containing action names.
|
|
177
178
|
|
|
178
179
|
Returns:
|
|
179
180
|
Label IDs for each tracker
|
|
@@ -854,7 +855,7 @@ class MaxHistoryTrackerFeaturizer(TrackerFeaturizer):
|
|
|
854
855
|
"""Creates an iterator over training examples from a tracker.
|
|
855
856
|
|
|
856
857
|
Args:
|
|
857
|
-
|
|
858
|
+
tracker: The tracker from which to extract training examples.
|
|
858
859
|
domain: The domain of the training data.
|
|
859
860
|
omit_unset_slots: If `True` do not include the initial values of slots.
|
|
860
861
|
ignore_action_unlikely_intent: Whether to remove `action_unlikely_intent`
|
rasa/core/persistor.py
CHANGED
|
@@ -314,7 +314,7 @@ class AWSPersistor(Persistor):
|
|
|
314
314
|
obj = self.s3.Object(self.bucket_name, model_path)
|
|
315
315
|
return obj.content_length
|
|
316
316
|
except Exception:
|
|
317
|
-
raise ModelNotFound()
|
|
317
|
+
raise ModelNotFound("Model not found")
|
|
318
318
|
|
|
319
319
|
def _retrieve_tar(
|
|
320
320
|
self, target_filename: str, target_path: Optional[str] = None
|
|
@@ -349,7 +349,7 @@ class AWSPersistor(Persistor):
|
|
|
349
349
|
target_filename=target_filename,
|
|
350
350
|
event_info=log,
|
|
351
351
|
)
|
|
352
|
-
raise ModelNotFound() from exc
|
|
352
|
+
raise ModelNotFound("Model not found") from exc
|
|
353
353
|
except exceptions.BotoCoreError as exc:
|
|
354
354
|
structlogger.error(
|
|
355
355
|
"aws_persistor.retrieve_tar.model_download_error",
|
|
@@ -357,7 +357,7 @@ class AWSPersistor(Persistor):
|
|
|
357
357
|
target_filename=target_filename,
|
|
358
358
|
event_info=log,
|
|
359
359
|
)
|
|
360
|
-
raise ModelNotFound() from exc
|
|
360
|
+
raise ModelNotFound("Model not found") from exc
|
|
361
361
|
|
|
362
362
|
|
|
363
363
|
class GCSPersistor(Persistor):
|
|
@@ -447,7 +447,7 @@ class GCSPersistor(Persistor):
|
|
|
447
447
|
blob = self.bucket.blob(target_filename)
|
|
448
448
|
return blob.size
|
|
449
449
|
except Exception:
|
|
450
|
-
raise ModelNotFound()
|
|
450
|
+
raise ModelNotFound("Model not found")
|
|
451
451
|
|
|
452
452
|
def _retrieve_tar(
|
|
453
453
|
self, target_filename: str, target_path: Optional[str] = None
|
|
@@ -481,7 +481,7 @@ class GCSPersistor(Persistor):
|
|
|
481
481
|
target_filename=target_filename,
|
|
482
482
|
event_info=log,
|
|
483
483
|
)
|
|
484
|
-
raise ModelNotFound() from exc
|
|
484
|
+
raise ModelNotFound("Model not found") from exc
|
|
485
485
|
|
|
486
486
|
|
|
487
487
|
class AzurePersistor(Persistor):
|
|
@@ -534,7 +534,7 @@ class AzurePersistor(Persistor):
|
|
|
534
534
|
properties = blob_client.get_blob_properties()
|
|
535
535
|
return properties.size
|
|
536
536
|
except Exception:
|
|
537
|
-
raise ModelNotFound()
|
|
537
|
+
raise ModelNotFound("Model not found")
|
|
538
538
|
|
|
539
539
|
def _retrieve_tar(
|
|
540
540
|
self, target_filename: Text, target_path: Optional[str] = None
|
|
@@ -570,4 +570,4 @@ class AzurePersistor(Persistor):
|
|
|
570
570
|
event_info=log,
|
|
571
571
|
exception=exc,
|
|
572
572
|
)
|
|
573
|
-
raise ModelNotFound() from exc
|
|
573
|
+
raise ModelNotFound("Model not found") from exc
|