rasa-pro 3.11.6__py3-none-any.whl → 3.11.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rasa-pro might be problematic. Click here for more details.

Files changed (77) hide show
  1. rasa/cli/run.py +10 -6
  2. rasa/cli/utils.py +7 -0
  3. rasa/core/channels/channel.py +93 -0
  4. rasa/core/channels/inspector/dist/assets/{arc-f0f8bd46.js → arc-62ea6ecb.js} +1 -1
  5. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-7162c77d.js → blockDiagram-38ab4fdb-133584f2.js} +1 -1
  6. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-b1d0d098.js → c4Diagram-3d4e48cf-3fdd847f.js} +1 -1
  7. rasa/core/channels/inspector/dist/assets/channel-6a3b6c3b.js +1 -0
  8. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-807a1b27.js → classDiagram-70f12bd4-fbbe018c.js} +1 -1
  9. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-5238dcdb.js → classDiagram-v2-f2320105-a4eb680a.js} +1 -1
  10. rasa/core/channels/inspector/dist/assets/clone-243bdc4d.js +1 -0
  11. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-75dfaa67.js → createText-2e5e7dd3-a0a4811e.js} +1 -1
  12. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-df20501d.js → edges-e0da2a9e-d6c66181.js} +1 -1
  13. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-13cf4797.js → erDiagram-9861fffd-f2062a78.js} +1 -1
  14. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-a4991264.js → flowDb-956e92f1-1a6bd8c6.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-ccecf773.js → flowDiagram-66a62f08-8c64ef56.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2fc14195.js +1 -0
  17. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-b5801783.js → flowchart-elk-definition-4a651766-b16259fa.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-161e079a.js → ganttDiagram-c361ad54-3bef87d8.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-f38e86a4.js → gitGraphDiagram-72cf32ee-c0776679.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/{graph-be6ef5d8.js → graph-af24022c.js} +1 -1
  21. rasa/core/channels/inspector/dist/assets/{index-3862675e-d9ce8994.js → index-3862675e-1f1f2ddf.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{index-7794b245.js → index-e799a83e.js} +129 -116
  23. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-5000a3dc.js → infoDiagram-f8f76790-c5d562c0.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-8ef0a17a.js → journeyDiagram-49397b02-8b3f9070.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{layout-d649bc98.js → layout-cc1e3a25.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{line-95add810.js → line-7f6d1f25.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{linear-f6025094.js → linear-4bacd66e.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-2e8531c4.js → mindmap-definition-fc14e90a-2926a2f0.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-918adfdb.js → pieDiagram-8a3498a8-05bf892e.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-cbd01797.js → quadrantDiagram-120e2f19-f700d7d2.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-6a8b877b.js → requirementDiagram-deff3bca-6eb3541f.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c377c3fe.js → sankeyDiagram-04a897e0-a47a81ed.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-ab9e9b7f.js → sequenceDiagram-704730f1-cf1ccf9f.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-5e6ae67d.js → stateDiagram-587899a1-405950fc.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-40643476.js → stateDiagram-v2-d93cdb3a-23d8e35b.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-afb8d108.js → styles-6aaf32cf-08c526c0.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-7edc9423.js → styles-9a916d00-8062abc7.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-c1d8f7e9.js → styles-c10674c1-0c776ed5.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-f494b2ef.js → svgDrawCommon-08f97a94-bb5daba8.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-11c7cdd0.js → timeline-definition-85554ec2-83421f60.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-3f191ec1.js → xychartDiagram-e933f94c-dad4ea79.js} +1 -1
  42. rasa/core/channels/inspector/dist/index.html +1 -1
  43. rasa/core/channels/inspector/src/components/Chat.tsx +23 -2
  44. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +2 -5
  45. rasa/core/channels/inspector/src/helpers/conversation.ts +16 -0
  46. rasa/core/channels/inspector/src/types.ts +1 -1
  47. rasa/core/channels/voice_ready/audiocodes.py +64 -28
  48. rasa/core/channels/voice_ready/jambonz.py +29 -8
  49. rasa/core/channels/voice_ready/jambonz_protocol.py +4 -0
  50. rasa/core/channels/voice_ready/twilio_voice.py +56 -8
  51. rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
  52. rasa/core/channels/voice_stream/tts/azure.py +13 -5
  53. rasa/core/channels/voice_stream/twilio_media_streams.py +110 -32
  54. rasa/core/channels/voice_stream/voice_channel.py +30 -30
  55. rasa/core/policies/intentless_policy.py +5 -59
  56. rasa/dialogue_understanding/generator/nlu_command_adapter.py +1 -1
  57. rasa/dialogue_understanding/processor/command_processor.py +20 -5
  58. rasa/dialogue_understanding/processor/command_processor_component.py +5 -2
  59. rasa/e2e_test/utils/validation.py +3 -3
  60. rasa/engine/validation.py +37 -2
  61. rasa/model_training.py +2 -1
  62. rasa/shared/constants.py +3 -0
  63. rasa/shared/core/domain.py +12 -3
  64. rasa/shared/core/policies/__init__.py +0 -0
  65. rasa/shared/core/policies/utils.py +87 -0
  66. rasa/tracing/instrumentation/attribute_extractors.py +2 -0
  67. rasa/version.py +1 -1
  68. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/METADATA +6 -7
  69. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/RECORD +72 -71
  70. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/WHEEL +1 -1
  71. README.md +0 -41
  72. rasa/core/channels/inspector/dist/assets/channel-e265ea59.js +0 -1
  73. rasa/core/channels/inspector/dist/assets/clone-21f8a43d.js +0 -1
  74. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-5c8ce12d.js +0 -1
  75. rasa/keys +0 -1
  76. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/NOTICE +0 -0
  77. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/entry_points.txt +0 -0
@@ -2,6 +2,7 @@ import { Box, Button, Flex, Heading, Text } from "@chakra-ui/react";
2
2
  import mermaid from "mermaid";
3
3
  import { useOurTheme } from "../theme";
4
4
  import { formatFlow } from "../helpers/formatters";
5
+ import { restartConversation } from "../helpers/conversation";
5
6
  import { useEffect, useRef, useState } from "react";
6
7
  import { Flow, Slot, Stack } from "../types";
7
8
  import { NoActiveFlow } from "./NoActiveFlow";
@@ -51,11 +52,7 @@ export const DiagramFlow = ({ stackFrame, stepTrail, flows, slots }: Props) => {
51
52
  }, [text, flow, slots, stackFrame]);
52
53
 
53
54
  const handleRestartConversation = () => {
54
- // unset the sender id from the query parameters
55
- const url = new URL(window.location.href);
56
- url.searchParams.delete("sender");
57
- window.history.pushState(null, "", url.toString());
58
- location.reload();
55
+ restartConversation();
59
56
  };
60
57
 
61
58
  const scrollSx = {
@@ -0,0 +1,16 @@
1
+ export const restartConversation = () => {
2
+ // unset the sender id from the query parameters
3
+ const url = new URL(window.location.href);
4
+ url.searchParams.delete("sender");
5
+ window.history.pushState(null, "", url.toString());
6
+ location.reload();
7
+ };
8
+
9
+ // Make the function available on the window object
10
+ declare global {
11
+ interface Window {
12
+ restartConversation: typeof restartConversation;
13
+ }
14
+ }
15
+
16
+ window.restartConversation = restartConversation;
@@ -5,7 +5,7 @@ export interface Slot {
5
5
  }
6
6
 
7
7
  export interface Event {
8
- event: "user" | "bot" | "flow_completed" | "flow_started" | "stack" | "restart";
8
+ event: "user" | "bot" | "flow_completed" | "flow_started" | "stack" | "restart" | "session_ended";
9
9
  text?: string;
10
10
  timestamp: string;
11
11
  update?: string;
@@ -1,28 +1,29 @@
1
1
  import asyncio
2
2
  import copy
3
- from datetime import datetime, timezone, timedelta
3
+ import hmac
4
4
  import json
5
5
  import uuid
6
6
  from collections import defaultdict
7
7
  from dataclasses import asdict
8
+ from datetime import datetime, timedelta, timezone
8
9
  from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Text, Union
9
10
 
10
11
  import structlog
11
12
  from jsonschema import ValidationError, validate
13
+ from sanic import Blueprint, response
14
+ from sanic.exceptions import NotFound, SanicException, ServerError
15
+ from sanic.request import Request
16
+ from sanic.response import HTTPResponse
17
+
12
18
  from rasa.core import jobs
13
19
  from rasa.core.channels.channel import InputChannel, OutputChannel, UserMessage
14
20
  from rasa.core.channels.voice_ready.utils import (
15
- validate_voice_license_scope,
16
21
  CallParameters,
22
+ validate_voice_license_scope,
17
23
  )
18
24
  from rasa.shared.constants import INTENT_MESSAGE_PREFIX
19
25
  from rasa.shared.core.constants import USER_INTENT_SESSION_START
20
26
  from rasa.shared.exceptions import RasaException
21
- from sanic import Blueprint, response
22
- from sanic.exceptions import NotFound, SanicException, ServerError
23
- from sanic.request import Request
24
- from sanic.response import HTTPResponse
25
-
26
27
  from rasa.utils.io import remove_emojis
27
28
 
28
29
  structlogger = structlog.get_logger()
@@ -95,10 +96,13 @@ class Conversation:
95
96
  event_params = {"value": event["value"]}
96
97
  text += json.dumps(event_params)
97
98
  else:
98
- structlogger.warning(
99
- "audiocodes.handle.event.unknown_event", event_payload=event
100
- )
101
- return ""
99
+ # handle other events described by Audiocodes
100
+ # https://techdocs.audiocodes.com/voice-ai-connect/#VAIG_Combined/inactivity-detection.htm?TocPath=Bot%2520integration%257CReceiving%2520notifications%257C_____3
101
+ text = f"{INTENT_MESSAGE_PREFIX}vaig_event_{event['name']}"
102
+ event_params = {**event.get("parameters", {})}
103
+ if "value" in event:
104
+ event_params["value"] = event["value"]
105
+ text += json.dumps(event_params)
102
106
 
103
107
  return text
104
108
 
@@ -114,11 +118,21 @@ class Conversation:
114
118
  async def handle_activities(
115
119
  self,
116
120
  message: Dict[Text, Any],
121
+ input_channel_name: str,
117
122
  output_channel: OutputChannel,
118
123
  on_new_message: Callable[[UserMessage], Awaitable[Any]],
119
124
  ) -> None:
120
125
  """Handle activities sent by Audiocodes."""
121
126
  structlogger.debug("audiocodes.handle.activities")
127
+ if input_channel_name == "":
128
+ structlogger.warning(
129
+ "audiocodes.handle.activities.empty_input_channel_name",
130
+ event_info=(
131
+ f"Audiocodes input channel name is empty "
132
+ f"for conversation {self.conversation_id}"
133
+ ),
134
+ )
135
+
122
136
  for activity in message["activities"]:
123
137
  text = None
124
138
  if activity[ACTIVITY_ID_KEY] in self.activity_ids:
@@ -142,6 +156,7 @@ class Conversation:
142
156
  metadata = self.get_metadata(activity)
143
157
  user_msg = UserMessage(
144
158
  text=text,
159
+ input_channel=input_channel_name,
145
160
  output_channel=output_channel,
146
161
  sender_id=self.conversation_id,
147
162
  metadata=metadata,
@@ -246,6 +261,9 @@ class AudiocodesInput(InputChannel):
246
261
  def _check_token(self, token: Optional[Text]) -> None:
247
262
  if not token:
248
263
  raise HttpUnauthorized("Authentication token required.")
264
+ if not hmac.compare_digest(str(token), str(self.token)):
265
+ structlogger.error("audiocodes.invalid_token", invalid_token=token)
266
+ raise HttpUnauthorized("Invalid authentication token.")
249
267
 
250
268
  def _get_conversation(
251
269
  self, token: Optional[Text], conversation_id: Text
@@ -388,7 +406,12 @@ class AudiocodesInput(InputChannel):
388
406
  # start a background task to handle activities
389
407
  self._create_task(
390
408
  conversation_id,
391
- conversation.handle_activities(request.json, ac_output, on_new_message),
409
+ conversation.handle_activities(
410
+ request.json,
411
+ input_channel_name=self.name(),
412
+ output_channel=ac_output,
413
+ on_new_message=on_new_message,
414
+ ),
392
415
  )
393
416
  return response.json(response_json)
394
417
 
@@ -401,23 +424,9 @@ class AudiocodesInput(InputChannel):
401
424
  Example of payload:
402
425
  {"conversation": <conversation_id>, "reason": Optional[Text]}.
403
426
  """
404
- self._get_conversation(request.token, conversation_id)
405
- reason = {"reason": request.json.get("reason")}
406
- await on_new_message(
407
- UserMessage(
408
- text=f"{INTENT_MESSAGE_PREFIX}session_end",
409
- output_channel=None,
410
- sender_id=conversation_id,
411
- metadata=reason,
412
- )
413
- )
414
- del self.conversations[conversation_id]
415
- structlogger.debug(
416
- "audiocodes.disconnect",
417
- conversation=conversation_id,
418
- request=request.json,
427
+ return await self._handle_disconnect(
428
+ request, conversation_id, on_new_message
419
429
  )
420
- return response.json({})
421
430
 
422
431
  @ac_webhook.route("/conversation/<conversation_id>/keepalive", methods=["POST"])
423
432
  async def keepalive(request: Request, conversation_id: Text) -> HTTPResponse:
@@ -432,6 +441,32 @@ class AudiocodesInput(InputChannel):
432
441
 
433
442
  return ac_webhook
434
443
 
444
+ async def _handle_disconnect(
445
+ self,
446
+ request: Request,
447
+ conversation_id: Text,
448
+ on_new_message: Callable[[UserMessage], Awaitable[Any]],
449
+ ) -> HTTPResponse:
450
+ """Triggered when the call is disconnected."""
451
+ self._get_conversation(request.token, conversation_id)
452
+ reason = {"reason": request.json.get("reason")}
453
+ await on_new_message(
454
+ UserMessage(
455
+ text=f"{INTENT_MESSAGE_PREFIX}session_end",
456
+ input_channel=self.name(),
457
+ output_channel=None,
458
+ sender_id=conversation_id,
459
+ metadata=reason,
460
+ )
461
+ )
462
+ del self.conversations[conversation_id]
463
+ structlogger.debug(
464
+ "audiocodes.disconnect",
465
+ conversation=conversation_id,
466
+ request=request.json,
467
+ )
468
+ return response.json({})
469
+
435
470
 
436
471
  class AudiocodesOutput(OutputChannel):
437
472
  @classmethod
@@ -439,6 +474,7 @@ class AudiocodesOutput(OutputChannel):
439
474
  return CHANNEL_NAME
440
475
 
441
476
  def __init__(self) -> None:
477
+ super().__init__()
442
478
  self.messages: List[Dict] = []
443
479
 
444
480
  async def add_message(self, message: Dict) -> None:
@@ -1,25 +1,30 @@
1
1
  from typing import Any, Awaitable, Callable, Dict, List, Optional, Text
2
2
 
3
3
  import structlog
4
- from rasa.core.channels.channel import InputChannel, OutputChannel, UserMessage
4
+ from sanic import Blueprint, Websocket, response # type: ignore[attr-defined]
5
+ from sanic.request import Request
6
+ from sanic.response import HTTPResponse
7
+
8
+ from rasa.core.channels.channel import (
9
+ InputChannel,
10
+ OutputChannel,
11
+ UserMessage,
12
+ requires_basic_auth,
13
+ )
5
14
  from rasa.core.channels.voice_ready.jambonz_protocol import (
15
+ CHANNEL_NAME,
6
16
  send_ws_text_message,
7
17
  websocket_message_handler,
8
18
  send_ws_hangup_message,
9
19
  )
10
20
  from rasa.core.channels.voice_ready.utils import validate_voice_license_scope
11
21
  from rasa.shared.exceptions import RasaException
12
- from sanic import Blueprint, response, Websocket # type: ignore[attr-defined]
13
- from sanic.request import Request
14
- from sanic.response import HTTPResponse
15
22
 
16
23
  from rasa.shared.utils.common import mark_as_beta_feature
17
24
  from rasa.utils.io import remove_emojis
18
25
 
19
26
  structlogger = structlog.get_logger()
20
27
 
21
- CHANNEL_NAME = "jambonz"
22
-
23
28
  DEFAULT_HANGUP_DELAY_SECONDS = 1
24
29
 
25
30
 
@@ -32,12 +37,27 @@ class JambonzVoiceReadyInput(InputChannel):
32
37
 
33
38
  @classmethod
34
39
  def from_credentials(cls, credentials: Optional[Dict[Text, Any]]) -> InputChannel:
35
- return cls()
40
+ if not credentials:
41
+ return cls()
42
+
43
+ username = credentials.get("username")
44
+ password = credentials.get("password")
45
+ if (username is None) != (password is None):
46
+ raise RasaException(
47
+ "In Jambonz channel, either both username and password "
48
+ "or neither should be provided. "
49
+ )
36
50
 
37
- def __init__(self) -> None:
51
+ return cls(username, password)
52
+
53
+ def __init__(
54
+ self, username: Optional[Text] = None, password: Optional[Text] = None
55
+ ) -> None:
38
56
  """Initializes the JambonzVoiceReadyInput channel."""
39
57
  mark_as_beta_feature("Jambonz Channel")
40
58
  validate_voice_license_scope()
59
+ self.username = username
60
+ self.password = password
41
61
 
42
62
  def blueprint(
43
63
  self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
@@ -50,6 +70,7 @@ class JambonzVoiceReadyInput(InputChannel):
50
70
  return response.json({"status": "ok"})
51
71
 
52
72
  @jambonz_webhook.websocket("/websocket", subprotocols=["ws.jambonz.org"]) # type: ignore
73
+ @requires_basic_auth(self.username, self.password)
53
74
  async def websocket(request: Request, ws: Websocket) -> None:
54
75
  """Triggered on new websocket connection."""
55
76
  async for message in ws:
@@ -11,6 +11,7 @@ from sanic import Websocket # type: ignore[attr-defined]
11
11
 
12
12
 
13
13
  structlogger = structlog.get_logger()
14
+ CHANNEL_NAME = "jambonz"
14
15
 
15
16
 
16
17
  @dataclass
@@ -207,6 +208,7 @@ async def handle_new_session(
207
208
  output_channel=output_channel,
208
209
  sender_id=message.call_sid,
209
210
  metadata=asdict(message.call_params),
211
+ input_channel=CHANNEL_NAME,
210
212
  )
211
213
  await send_config_ack(message.message_id, ws)
212
214
  await on_new_message(user_msg)
@@ -239,6 +241,7 @@ async def handle_gather_completed(
239
241
  output_channel = JambonzWebsocketOutput(ws, transcript_result.call_sid)
240
242
  user_msg = UserMessage(
241
243
  text=most_likely_transcript.text,
244
+ input_channel=CHANNEL_NAME,
242
245
  output_channel=output_channel,
243
246
  sender_id=transcript_result.call_sid,
244
247
  metadata={},
@@ -289,6 +292,7 @@ async def handle_call_status(
289
292
  output_channel = JambonzWebsocketOutput(ws, call_status.call_sid)
290
293
  user_msg = UserMessage(
291
294
  text="/session_end",
295
+ input_channel=CHANNEL_NAME,
292
296
  output_channel=output_channel,
293
297
  sender_id=call_status.call_sid,
294
298
  metadata={},
@@ -1,25 +1,31 @@
1
+ from dataclasses import asdict
2
+ from typing import Any, Awaitable, Callable, Dict, List, Optional, Text
3
+
4
+ import structlog
1
5
  from sanic import Blueprint, response
2
6
  from sanic.request import Request, RequestParameters
3
7
  from sanic.response import HTTPResponse
4
- from twilio.twiml.voice_response import VoiceResponse, Gather
5
- from typing import Text, Callable, Awaitable, List, Any, Dict, Optional
6
- from dataclasses import asdict
8
+ from twilio.twiml.voice_response import Gather, VoiceResponse
7
9
 
8
- import structlog
9
- import rasa.utils.io
10
10
  import rasa.shared.utils.io
11
- from rasa.shared.core.events import BotUttered
12
- from rasa.shared.exceptions import InvalidConfigException
11
+ import rasa.utils.io
13
12
  from rasa.core.channels.channel import (
14
- InputChannel,
15
13
  CollectingOutputChannel,
14
+ InputChannel,
16
15
  UserMessage,
16
+ create_auth_requested_response_provider,
17
+ requires_basic_auth,
17
18
  )
18
19
  from rasa.core.channels.voice_ready.utils import CallParameters
20
+ from rasa.shared.core.events import BotUttered
21
+ from rasa.shared.exceptions import InvalidConfigException, RasaException
19
22
 
20
23
  logger = structlog.get_logger(__name__)
21
24
 
22
25
 
26
+ TWILIO_VOICE_PATH = "webhooks/twilio_voice/webhook"
27
+
28
+
23
29
  def map_call_params(form: RequestParameters) -> CallParameters:
24
30
  """Map the Audiocodes parameters to the CallParameters dataclass."""
25
31
  return CallParameters(
@@ -119,6 +125,14 @@ class TwilioVoiceInput(InputChannel):
119
125
  """Load custom configurations."""
120
126
  credentials = credentials or {}
121
127
 
128
+ username = credentials.get("username")
129
+ password = credentials.get("password")
130
+ if (username is None) != (password is None):
131
+ raise RasaException(
132
+ "In TwilioVoice channel, either both username and password "
133
+ "or neither should be provided. "
134
+ )
135
+
122
136
  return cls(
123
137
  credentials.get(
124
138
  "reprompt_fallback_phrase",
@@ -128,6 +142,8 @@ class TwilioVoiceInput(InputChannel):
128
142
  credentials.get("speech_timeout", "5"),
129
143
  credentials.get("speech_model", "default"),
130
144
  credentials.get("enhanced", "false"),
145
+ username=username,
146
+ password=password,
131
147
  )
132
148
 
133
149
  def __init__(
@@ -137,6 +153,8 @@ class TwilioVoiceInput(InputChannel):
137
153
  speech_timeout: Text = "5",
138
154
  speech_model: Text = "default",
139
155
  enhanced: Text = "false",
156
+ username: Optional[Text] = None,
157
+ password: Optional[Text] = None,
140
158
  ) -> None:
141
159
  """Creates a connection to Twilio voice.
142
160
 
@@ -152,6 +170,8 @@ class TwilioVoiceInput(InputChannel):
152
170
  self.speech_timeout = speech_timeout
153
171
  self.speech_model = speech_model
154
172
  self.enhanced = enhanced
173
+ self.username = username
174
+ self.password = password
155
175
 
156
176
  self._validate_configuration()
157
177
 
@@ -160,6 +180,9 @@ class TwilioVoiceInput(InputChannel):
160
180
  if self.assistant_voice not in self.SUPPORTED_VOICES:
161
181
  self._raise_invalid_voice_exception()
162
182
 
183
+ if (self.username is None) != (self.password is None):
184
+ self._raise_invalid_credentials_exception()
185
+
163
186
  try:
164
187
  int(self.speech_timeout)
165
188
  except ValueError:
@@ -245,6 +268,13 @@ class TwilioVoiceInput(InputChannel):
245
268
  return response.json({"status": "ok"})
246
269
 
247
270
  @twilio_voice_webhook.route("/webhook", methods=["POST"])
271
+ @requires_basic_auth(
272
+ username=self.username,
273
+ password=self.password,
274
+ auth_request_provider=create_auth_requested_response_provider(
275
+ TWILIO_VOICE_PATH
276
+ ),
277
+ )
248
278
  async def receive(request: Request) -> HTTPResponse:
249
279
  sender_id = request.form.get("From")
250
280
  text = request.form.get("SpeechResult")
@@ -309,6 +339,11 @@ class TwilioVoiceInput(InputChannel):
309
339
  twilio_response = self._build_twilio_voice_response(
310
340
  [{"text": last_response_text}]
311
341
  )
342
+
343
+ logger.debug(
344
+ "twilio_voice.webhook.twilio_response",
345
+ twilio_response=str(twilio_response),
346
+ )
312
347
  return response.text(str(twilio_response), content_type="text/xml")
313
348
 
314
349
  return twilio_voice_webhook
@@ -328,6 +363,13 @@ class TwilioVoiceInput(InputChannel):
328
363
  enhanced=self.enhanced,
329
364
  )
330
365
 
366
+ if not messages:
367
+ # In case bot has a greet message disabled
368
+ # or if the bot is not configured to send an initial message
369
+ # we need to send a voice response with speech settings
370
+ voice_response.append(gather)
371
+ return voice_response
372
+
331
373
  # Add pauses between messages.
332
374
  # Add a listener to the last message to listen for user response.
333
375
  for i, message in enumerate(messages):
@@ -346,6 +388,12 @@ class TwilioVoiceInput(InputChannel):
346
388
 
347
389
  return voice_response
348
390
 
391
+ def _raise_invalid_credentials_exception(self) -> None:
392
+ raise InvalidConfigException(
393
+ "In TwilioVoice channel, either both username and password "
394
+ "or neither should be provided. "
395
+ )
396
+
349
397
 
350
398
  class TwilioVoiceCollectingOutputChannel(CollectingOutputChannel):
351
399
  """Output channel that collects send messages in a list.
@@ -16,3 +16,8 @@ class NewTranscript(ASREvent):
16
16
  @dataclass
17
17
  class UserIsSpeaking(ASREvent):
18
18
  pass
19
+
20
+
21
+ @dataclass
22
+ class UserSilence(ASREvent):
23
+ pass
@@ -1,6 +1,6 @@
1
1
  import os
2
- from typing import AsyncIterator, Dict, Optional
3
2
  from dataclasses import dataclass
3
+ from typing import AsyncIterator, Dict, Optional
4
4
 
5
5
  import aiohttp
6
6
  import structlog
@@ -15,7 +15,6 @@ from rasa.core.channels.voice_stream.tts.tts_engine import (
15
15
  from rasa.shared.constants import AZURE_SPEECH_API_KEY_ENV_VAR
16
16
  from rasa.shared.exceptions import ConnectionException
17
17
 
18
-
19
18
  structlogger = structlog.get_logger()
20
19
 
21
20
 
@@ -54,13 +53,22 @@ class AzureTTS(TTSEngine[AzureTTSConfig]):
54
53
  async for data in response.content.iter_chunked(1024):
55
54
  yield self.engine_bytes_to_rasa_audio_bytes(data)
56
55
  return
56
+ elif response.status == 401:
57
+ structlogger.error(
58
+ "azure.synthesize.rest.authentication_failed",
59
+ status_code=response.status,
60
+ )
61
+ raise TTSError(
62
+ f"Authentication failed. Please check your API key: {response.status}" # noqa: E501
63
+ )
57
64
  else:
65
+ response_text = await response.text()
58
66
  structlogger.error(
59
67
  "azure.synthesize.rest.failed",
60
68
  status_code=response.status,
61
- msg=response.text(),
69
+ msg=response_text,
62
70
  )
63
- raise TTSError(f"TTS failed: {response.text()}")
71
+ raise TTSError(f"TTS failed: {response_text}")
64
72
  except ClientConnectorError as e:
65
73
  raise TTSError(e)
66
74
  except TimeoutError as e:
@@ -98,7 +106,7 @@ class AzureTTS(TTSEngine[AzureTTSConfig]):
98
106
  language="en-US",
99
107
  voice="en-US-JennyNeural",
100
108
  timeout=10,
101
- speech_region="germanywestcentral",
109
+ speech_region="eastus",
102
110
  )
103
111
 
104
112
  @classmethod