rasa-pro 3.12.0rc2__py3-none-any.whl → 3.12.1__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/dialogue_understanding_test.py +5 -8
- rasa/cli/llm_fine_tuning.py +47 -12
- rasa/cli/train.py +3 -0
- rasa/cli/utils.py +6 -0
- rasa/core/channels/development_inspector.py +77 -21
- rasa/core/channels/inspector/dist/assets/{arc-f0f8bd46.js → arc-9f1365dc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-7162c77d.js → blockDiagram-38ab4fdb-e0f81b12.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-b1d0d098.js → c4Diagram-3d4e48cf-9deaee1c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-44956714.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-807a1b27.js → classDiagram-70f12bd4-20450a96.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-5238dcdb.js → classDiagram-v2-f2320105-749d2abf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-a9475142.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-75dfaa67.js → createText-2e5e7dd3-bef0b38c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-df20501d.js → edges-e0da2a9e-943801a7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-13cf4797.js → erDiagram-9861fffd-d523a948.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-a4991264.js → flowDb-956e92f1-54e4cf19.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-ccecf773.js → flowDiagram-66a62f08-48bfbbe8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-43fa749a.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-b5801783.js → flowchart-elk-definition-4a651766-17c30827.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-161e079a.js → ganttDiagram-c361ad54-43086f2d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-f38e86a4.js → gitGraphDiagram-72cf32ee-5c8b693e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-be6ef5d8.js → graph-41a90d26.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-d9ce8994.js → index-3862675e-b43eeae9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-7794b245.js → index-e8affe45.js} +155 -155
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-5000a3dc.js → infoDiagram-f8f76790-0b20676b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-8ef0a17a.js → journeyDiagram-49397b02-39bce7b5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-d649bc98.js → layout-dc8eeea4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-95add810.js → line-c4d2e756.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-f6025094.js → linear-86f6f2d9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-2e8531c4.js → mindmap-definition-fc14e90a-4216f771.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-918adfdb.js → pieDiagram-8a3498a8-1a0cfa96.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-cbd01797.js → quadrantDiagram-120e2f19-f91e67cf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-6a8b877b.js → requirementDiagram-deff3bca-d4046bed.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c377c3fe.js → sankeyDiagram-04a897e0-2cf6d1d7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-ab9e9b7f.js → sequenceDiagram-704730f1-751ac4f5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-5e6ae67d.js → stateDiagram-587899a1-f734f4d4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-40643476.js → stateDiagram-v2-d93cdb3a-91c65710.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-afb8d108.js → styles-6aaf32cf-e0cff7be.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-7edc9423.js → styles-9a916d00-c8029e5d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-c1d8f7e9.js → styles-c10674c1-114f312a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-f494b2ef.js → svgDrawCommon-08f97a94-b7b9dc00.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-11c7cdd0.js → timeline-definition-85554ec2-9536d189.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-3f191ec1.js → xychartDiagram-e933f94c-bf3b0f36.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/package.json +1 -0
- rasa/core/channels/inspector/src/App.tsx +15 -2
- rasa/core/channels/inspector/src/components/RasaLogo.tsx +31 -0
- rasa/core/channels/inspector/src/components/RecruitmentPanel.tsx +68 -0
- rasa/core/channels/inspector/src/components/Welcome.tsx +19 -13
- rasa/core/channels/inspector/yarn.lock +5 -0
- rasa/core/channels/voice_ready/audiocodes.py +2 -2
- rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
- rasa/core/channels/voice_stream/audiocodes.py +63 -35
- rasa/core/channels/voice_stream/call_state.py +3 -9
- rasa/core/channels/voice_stream/genesys.py +40 -55
- rasa/core/channels/voice_stream/voice_channel.py +61 -39
- rasa/core/tracker_store.py +123 -34
- rasa/dialogue_understanding/commands/set_slot_command.py +1 -0
- rasa/dialogue_understanding/commands/utils.py +1 -4
- rasa/dialogue_understanding/generator/command_parser.py +41 -0
- rasa/dialogue_understanding/generator/constants.py +7 -2
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +33 -3
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +29 -48
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +23 -50
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +76 -24
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +32 -18
- rasa/dialogue_understanding/processor/command_processor.py +59 -20
- rasa/dialogue_understanding/stack/utils.py +11 -6
- rasa/engine/language.py +67 -25
- rasa/engine/validation.py +2 -0
- rasa/llm_fine_tuning/conversations.py +3 -31
- rasa/llm_fine_tuning/llm_data_preparation_module.py +5 -3
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +18 -13
- rasa/llm_fine_tuning/paraphrasing_module.py +6 -2
- rasa/llm_fine_tuning/train_test_split_module.py +27 -27
- rasa/llm_fine_tuning/utils.py +7 -0
- rasa/model_training.py +3 -1
- rasa/server.py +1 -0
- rasa/shared/constants.py +4 -0
- rasa/shared/core/domain.py +6 -0
- rasa/shared/importers/importer.py +9 -1
- rasa/shared/providers/_configs/azure_entra_id_config.py +8 -8
- rasa/shared/providers/llm/litellm_router_llm_client.py +1 -0
- rasa/shared/providers/router/_base_litellm_router_client.py +38 -7
- rasa/shared/utils/common.py +14 -0
- rasa/shared/utils/llm.py +69 -13
- rasa/telemetry.py +13 -3
- rasa/tracing/instrumentation/attribute_extractors.py +2 -5
- rasa/validator.py +4 -4
- rasa/version.py +1 -1
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.1.dist-info}/METADATA +2 -2
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.1.dist-info}/RECORD +95 -94
- rasa/core/channels/inspector/dist/assets/channel-e265ea59.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-21f8a43d.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-5c8ce12d.js +0 -1
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +0 -68
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.1.dist-info}/entry_points.txt +0 -0
|
@@ -13,6 +13,7 @@ import { DialogueStack } from "./components/DialogueStack";
|
|
|
13
13
|
import { DialougeInformation } from "./components/DialogueInformation";
|
|
14
14
|
import { LoadingSpinner } from "./components/LoadingSpinner";
|
|
15
15
|
import { DiagramFlow } from "./components/DiagramFlow";
|
|
16
|
+
import { RecruitmentPanel } from "./components/RecruitmentPanel";
|
|
16
17
|
import { formatSlots } from "./helpers/formatters";
|
|
17
18
|
import { Slot, Stack, Event, Flow, SelectedStack, Tracker } from "./types";
|
|
18
19
|
import {
|
|
@@ -35,6 +36,9 @@ export function App() {
|
|
|
35
36
|
const [stack, setStack] = useState<Stack[]>([]);
|
|
36
37
|
const [frame, setFrame] = useState<SelectedStack | undefined>(undefined);
|
|
37
38
|
|
|
39
|
+
// State to control the visibility of the RecruitmentPanel
|
|
40
|
+
const [showRecruitmentPanel, setShowRecruitmentPanel] = useState(true);
|
|
41
|
+
|
|
38
42
|
// we only show the transcript if we are not on the socket io channel
|
|
39
43
|
// on the socketio channel, we show the chat component instead
|
|
40
44
|
const shouldShowTranscript = !window.location.href.includes("socketio");
|
|
@@ -165,7 +169,9 @@ export function App() {
|
|
|
165
169
|
height: "100%",
|
|
166
170
|
overflow: "hidden",
|
|
167
171
|
gridTemplateColumns: "1fr",
|
|
168
|
-
gridTemplateRows:
|
|
172
|
+
gridTemplateRows: showRecruitmentPanel
|
|
173
|
+
? "max-content max-content minmax(10rem, auto)"
|
|
174
|
+
: "max-content minmax(10rem, 17.5rem) minmax(10rem, auto)",
|
|
169
175
|
gridRowGap: rasaSpace[1],
|
|
170
176
|
};
|
|
171
177
|
|
|
@@ -177,13 +183,20 @@ export function App() {
|
|
|
177
183
|
});
|
|
178
184
|
};
|
|
179
185
|
|
|
186
|
+
const handleCloseRecruitmentPanel = () => {
|
|
187
|
+
setShowRecruitmentPanel(false);
|
|
188
|
+
};
|
|
189
|
+
|
|
180
190
|
if (!rasaChatSessionId && !window.location.href.includes("socketio")) return <LoadingSpinner />;
|
|
181
191
|
|
|
182
192
|
return (
|
|
183
193
|
<Grid sx={gridSx}>
|
|
184
194
|
<GridItem overflow="hidden">
|
|
185
195
|
<Grid sx={leftColumnSx}>
|
|
186
|
-
<Welcome sx={boxSx} />
|
|
196
|
+
<Welcome sx={boxSx} isRecruitmentVisible={showRecruitmentPanel} />
|
|
197
|
+
{showRecruitmentPanel && (
|
|
198
|
+
<RecruitmentPanel onClose={handleCloseRecruitmentPanel} />
|
|
199
|
+
)}
|
|
187
200
|
<DialogueStack
|
|
188
201
|
sx={boxSx}
|
|
189
202
|
stack={stack}
|
|
@@ -30,3 +30,34 @@ export const RasaLogo = (props: BoxProps) => {
|
|
|
30
30
|
</Box>
|
|
31
31
|
);
|
|
32
32
|
};
|
|
33
|
+
|
|
34
|
+
export const RasaLogoDark = (props: BoxProps) => {
|
|
35
|
+
return (
|
|
36
|
+
<Box
|
|
37
|
+
as="svg"
|
|
38
|
+
{...props}
|
|
39
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
40
|
+
width="41"
|
|
41
|
+
height="51"
|
|
42
|
+
fill="none"
|
|
43
|
+
viewBox="0 0 41 51"
|
|
44
|
+
>
|
|
45
|
+
<path
|
|
46
|
+
fill="#7622D2"
|
|
47
|
+
fillRule="evenodd"
|
|
48
|
+
d="M34.041 10.59V7.508H21.385v12.847h3.037v-3.867h6.582v3.854h3.037v-9.763.013zm-3.037 2.827h-6.582V10.59h6.582v2.826zM19.36 29.74V35.52H6.956v-3.083h9.366V30.64H6.956v-7.965H19.36v3.083H9.994v1.798h9.366v2.184zM34.041 25.75v-3.084H21.385v12.847h3.037v-3.867h6.582V35.5h3.037v-9.764.013zm-3.037 2.826h-6.582v-2.827h6.582v2.827z"
|
|
49
|
+
clipRule="evenodd"
|
|
50
|
+
></path>
|
|
51
|
+
<path
|
|
52
|
+
fill="#7622D2"
|
|
53
|
+
d="M36.826 4.689v33.578h-5.248v5.724l-9.487-5.318-.744-.417H4.179V4.69h32.654-.007zm3.298-3.34H.881v40.258h19.618l14.368 8.054v-8.054h5.25V1.349h.007z"
|
|
54
|
+
></path>
|
|
55
|
+
<path
|
|
56
|
+
fill="#7622D2"
|
|
57
|
+
fillRule="evenodd"
|
|
58
|
+
d="M15.287 15.464l3.888-1.436.185-.074V7.515H6.956v.257l-.028 12.59h3.038V17.43l2.278-.838 3.417 3.77h3.752l-4.126-4.897zM9.97 14.15v-3.55h6.351V11.8l-6.351 2.348z"
|
|
59
|
+
clipRule="evenodd"
|
|
60
|
+
></path>
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// RecruitmentPanel.tsx
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Flex,
|
|
5
|
+
Heading,
|
|
6
|
+
IconButton,
|
|
7
|
+
Text,
|
|
8
|
+
Button,
|
|
9
|
+
} from "@chakra-ui/react";
|
|
10
|
+
import { CloseIcon } from "@chakra-ui/icons";
|
|
11
|
+
import { useOurTheme } from "../theme";
|
|
12
|
+
|
|
13
|
+
interface RecruitmentPanelProps {
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const RecruitmentPanel: React.FC<RecruitmentPanelProps> = ({ onClose }) => {
|
|
18
|
+
const { rasaRadii } = useOurTheme();
|
|
19
|
+
|
|
20
|
+
const textColor = "white";
|
|
21
|
+
const iconColor = "white";
|
|
22
|
+
|
|
23
|
+
const boxSx = {
|
|
24
|
+
borderRadius: rasaRadii.normal,
|
|
25
|
+
padding: "1rem",
|
|
26
|
+
bgGradient: "linear(to-b, #4E61E1, #7622D2)",
|
|
27
|
+
color: textColor,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box sx={boxSx}>
|
|
32
|
+
<Flex justify="space-between" align="center">
|
|
33
|
+
<Flex align="center">
|
|
34
|
+
<Heading as="h3" size="lg" fontWeight="bold" color={textColor}>
|
|
35
|
+
Help us Improve Rasa Pro!
|
|
36
|
+
</Heading>
|
|
37
|
+
</Flex>
|
|
38
|
+
<IconButton
|
|
39
|
+
aria-label="Close"
|
|
40
|
+
icon={<CloseIcon color={iconColor} />}
|
|
41
|
+
size="sm"
|
|
42
|
+
onClick={onClose}
|
|
43
|
+
bg="transparent"
|
|
44
|
+
_hover={{ bg: "rgba(255, 255, 255, 0.2)" }}
|
|
45
|
+
/>
|
|
46
|
+
</Flex>
|
|
47
|
+
<Flex align="center" mt="0.5rem" justify="space-between">
|
|
48
|
+
<Text fontSize="sm" color={textColor}>
|
|
49
|
+
We're looking for users to share their feedback.
|
|
50
|
+
</Text>
|
|
51
|
+
<Button
|
|
52
|
+
as="a"
|
|
53
|
+
href="https://calendly.com/alvaro-rasa/rasa-pro-installation"
|
|
54
|
+
target="_blank"
|
|
55
|
+
rel="noopener noreferrer"
|
|
56
|
+
color="#7622D2"
|
|
57
|
+
bg="white"
|
|
58
|
+
fontWeight="bold"
|
|
59
|
+
ml="1rem"
|
|
60
|
+
size="sm"
|
|
61
|
+
_hover={{ bg: "whiteAlpha.800" }}
|
|
62
|
+
>
|
|
63
|
+
Sign up
|
|
64
|
+
</Button>
|
|
65
|
+
</Flex>
|
|
66
|
+
</Box>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -5,33 +5,35 @@ import {
|
|
|
5
5
|
Heading,
|
|
6
6
|
Link,
|
|
7
7
|
Text,
|
|
8
|
-
useColorModeValue,
|
|
9
8
|
} from "@chakra-ui/react";
|
|
10
|
-
import { RasaLogo } from "./RasaLogo";
|
|
9
|
+
import { RasaLogo, RasaLogoDark } from "./RasaLogo";
|
|
11
10
|
import { useOurTheme } from "../theme";
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
interface WelcomeProps extends FlexProps {
|
|
13
|
+
isRecruitmentVisible?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Welcome = ({ sx, isRecruitmentVisible, ...props }: WelcomeProps) => {
|
|
14
17
|
const { rasaSpace } = useOurTheme();
|
|
15
18
|
|
|
16
19
|
const containerSx = {
|
|
17
20
|
...sx,
|
|
18
|
-
color:
|
|
19
|
-
|
|
21
|
+
color: isRecruitmentVisible ? "black" : "neutral.50",
|
|
22
|
+
bg: isRecruitmentVisible ? "white" : undefined,
|
|
23
|
+
bgGradient: isRecruitmentVisible ? undefined : "linear(to-b, #4E61E1, #7622D2)",
|
|
20
24
|
};
|
|
21
25
|
|
|
22
26
|
const linkSx = {
|
|
23
27
|
flexGrow: 0,
|
|
24
|
-
color:
|
|
28
|
+
color: isRecruitmentVisible ? "#0000EE" : "neutral.50",
|
|
29
|
+
textDecoration: "underline",
|
|
25
30
|
_hover: {
|
|
26
|
-
color:
|
|
31
|
+
color: isRecruitmentVisible
|
|
32
|
+
? "link.visited"
|
|
33
|
+
: "neutral.400",
|
|
27
34
|
},
|
|
28
35
|
};
|
|
29
36
|
|
|
30
|
-
const logoSx = {
|
|
31
|
-
flexShrink:0,
|
|
32
|
-
marginLeft: "auto"
|
|
33
|
-
};
|
|
34
|
-
|
|
35
37
|
return (
|
|
36
38
|
<Flex sx={containerSx} {...props}>
|
|
37
39
|
<Box>
|
|
@@ -48,7 +50,11 @@ export const Welcome = ({ sx, ...props }: FlexProps) => {
|
|
|
48
50
|
ml={rasaSpace[0.25]}
|
|
49
51
|
>Browse the docs</Link>
|
|
50
52
|
</Box>
|
|
51
|
-
|
|
53
|
+
{isRecruitmentVisible ? (
|
|
54
|
+
<RasaLogoDark sx={{ flexShrink: 0, marginLeft: "auto" }}/>
|
|
55
|
+
) : (
|
|
56
|
+
<RasaLogo sx={{ flexShrink: 0, marginLeft: "auto" }}/>
|
|
57
|
+
)}
|
|
52
58
|
</Flex>
|
|
53
59
|
);
|
|
54
60
|
};
|
|
@@ -531,6 +531,11 @@
|
|
|
531
531
|
dependencies:
|
|
532
532
|
"@chakra-ui/shared-utils" "2.0.5"
|
|
533
533
|
|
|
534
|
+
"@chakra-ui/icons@^2.2.4":
|
|
535
|
+
version "2.2.4"
|
|
536
|
+
resolved "https://registry.yarnpkg.com/@chakra-ui/icons/-/icons-2.2.4.tgz#fc3f59a7e377d6e4efdbe8ee0a3aec7f29a4ab32"
|
|
537
|
+
integrity sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==
|
|
538
|
+
|
|
534
539
|
"@chakra-ui/image@2.1.0":
|
|
535
540
|
version "2.1.0"
|
|
536
541
|
resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-2.1.0.tgz#6c205f1ca148e3bf58345b0b5d4eb3d959eb9f87"
|
|
@@ -47,8 +47,8 @@ def map_call_params(parameters: Dict[Text, Any]) -> CallParameters:
|
|
|
47
47
|
"""Map the Audiocodes parameters to the CallParameters dataclass."""
|
|
48
48
|
return CallParameters(
|
|
49
49
|
call_id=parameters.get("vaigConversationId"),
|
|
50
|
-
user_phone=parameters.get("
|
|
51
|
-
bot_phone=parameters.get("
|
|
50
|
+
user_phone=parameters.get("caller"),
|
|
51
|
+
bot_phone=parameters.get("callee"),
|
|
52
52
|
user_name=parameters.get("callerDisplayName"),
|
|
53
53
|
user_host=parameters.get("callerHost"),
|
|
54
54
|
bot_host=parameters.get("calleeHost"),
|
|
@@ -13,6 +13,7 @@ from sanic import ( # type: ignore[attr-defined]
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
from rasa.core.channels import UserMessage
|
|
16
|
+
from rasa.core.channels.voice_ready.audiocodes import map_call_params
|
|
16
17
|
from rasa.core.channels.voice_ready.utils import CallParameters
|
|
17
18
|
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
18
19
|
from rasa.core.channels.voice_stream.call_state import (
|
|
@@ -27,24 +28,29 @@ from rasa.core.channels.voice_stream.voice_channel import (
|
|
|
27
28
|
VoiceInputChannel,
|
|
28
29
|
VoiceOutputChannel,
|
|
29
30
|
)
|
|
31
|
+
from rasa.shared.utils.common import mark_as_beta_feature
|
|
30
32
|
|
|
31
33
|
logger = structlog.get_logger(__name__)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def map_call_params(data: Dict[Text, Any]) -> CallParameters:
|
|
35
|
-
"""Map the audiocodes stream parameters to the CallParameters dataclass."""
|
|
36
|
-
return CallParameters(
|
|
37
|
-
call_id=data["conversationId"],
|
|
38
|
-
user_phone=data["caller"],
|
|
39
|
-
# Bot phone is not available in the Audiocodes API
|
|
40
|
-
direction="inbound", # AudioCodes calls are always inbound
|
|
41
|
-
)
|
|
34
|
+
PREFERRED_AUDIO_FORMAT = "raw/mulaw"
|
|
42
35
|
|
|
43
36
|
|
|
44
37
|
class AudiocodesVoiceOutputChannel(VoiceOutputChannel):
|
|
45
38
|
@classmethod
|
|
46
39
|
def name(cls) -> str:
|
|
47
|
-
return "
|
|
40
|
+
return "audiocodes_stream"
|
|
41
|
+
|
|
42
|
+
def _ensure_stream_id(self) -> None:
|
|
43
|
+
"""Audiocodes requires a stream ID with playStream messages."""
|
|
44
|
+
if "stream_id" not in call_state.channel_data:
|
|
45
|
+
call_state.channel_data["stream_id"] = 0
|
|
46
|
+
|
|
47
|
+
def _increment_stream_id(self) -> None:
|
|
48
|
+
self._ensure_stream_id()
|
|
49
|
+
call_state.channel_data["stream_id"] += 1
|
|
50
|
+
|
|
51
|
+
def _get_stream_id(self) -> str:
|
|
52
|
+
self._ensure_stream_id()
|
|
53
|
+
return str(call_state.channel_data["stream_id"])
|
|
48
54
|
|
|
49
55
|
def rasa_audio_bytes_to_channel_bytes(
|
|
50
56
|
self, rasa_audio_bytes: RasaAudioBytes
|
|
@@ -55,7 +61,7 @@ class AudiocodesVoiceOutputChannel(VoiceOutputChannel):
|
|
|
55
61
|
media_message = json.dumps(
|
|
56
62
|
{
|
|
57
63
|
"type": "playStream.chunk",
|
|
58
|
-
"streamId":
|
|
64
|
+
"streamId": self._get_stream_id(),
|
|
59
65
|
"audioChunk": channel_bytes.decode("utf-8"),
|
|
60
66
|
}
|
|
61
67
|
)
|
|
@@ -63,14 +69,15 @@ class AudiocodesVoiceOutputChannel(VoiceOutputChannel):
|
|
|
63
69
|
|
|
64
70
|
async def send_start_marker(self, recipient_id: str) -> None:
|
|
65
71
|
"""Send playStream.start before first audio chunk."""
|
|
66
|
-
|
|
72
|
+
self._increment_stream_id()
|
|
67
73
|
media_message = json.dumps(
|
|
68
74
|
{
|
|
69
75
|
"type": "playStream.start",
|
|
70
|
-
"streamId":
|
|
76
|
+
"streamId": self._get_stream_id(),
|
|
77
|
+
"mediaFormat": PREFERRED_AUDIO_FORMAT,
|
|
71
78
|
}
|
|
72
79
|
)
|
|
73
|
-
logger.debug("Sending start marker", stream_id=
|
|
80
|
+
logger.debug("Sending start marker", stream_id=self._get_stream_id())
|
|
74
81
|
await self.voice_websocket.send(media_message)
|
|
75
82
|
|
|
76
83
|
async def send_intermediate_marker(self, recipient_id: str) -> None:
|
|
@@ -82,17 +89,27 @@ class AudiocodesVoiceOutputChannel(VoiceOutputChannel):
|
|
|
82
89
|
media_message = json.dumps(
|
|
83
90
|
{
|
|
84
91
|
"type": "playStream.stop",
|
|
85
|
-
"streamId":
|
|
92
|
+
"streamId": self._get_stream_id(),
|
|
86
93
|
}
|
|
87
94
|
)
|
|
88
|
-
logger.debug("Sending end marker", stream_id=
|
|
95
|
+
logger.debug("Sending end marker", stream_id=self._get_stream_id())
|
|
89
96
|
await self.voice_websocket.send(media_message)
|
|
90
97
|
|
|
91
98
|
|
|
92
99
|
class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
93
100
|
@classmethod
|
|
94
101
|
def name(cls) -> str:
|
|
95
|
-
return "
|
|
102
|
+
return "audiocodes_stream"
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
server_url: str,
|
|
107
|
+
asr_config: Dict,
|
|
108
|
+
tts_config: Dict,
|
|
109
|
+
monitor_silence: bool = False,
|
|
110
|
+
):
|
|
111
|
+
mark_as_beta_feature("Audiocodes (audiocodes_stream) Channel")
|
|
112
|
+
super().__init__(server_url, asr_config, tts_config, monitor_silence)
|
|
96
113
|
|
|
97
114
|
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
98
115
|
return RasaAudioBytes(base64.b64decode(input_bytes))
|
|
@@ -103,13 +120,23 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
103
120
|
async for message in channel_websocket:
|
|
104
121
|
data = json.loads(message)
|
|
105
122
|
if data["type"] == "session.initiate":
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
# contains info about mediaformats
|
|
124
|
+
logger.info(
|
|
125
|
+
"audiocodes_stream.collect_call_parameters.session.initiate",
|
|
126
|
+
data=data,
|
|
127
|
+
)
|
|
109
128
|
self._send_accepted(channel_websocket, data)
|
|
110
|
-
|
|
129
|
+
elif data["type"] == "activities":
|
|
130
|
+
activities = data["activities"]
|
|
131
|
+
for activity in activities:
|
|
132
|
+
logger.debug(
|
|
133
|
+
"audiocodes_stream.collect_call_parameters.activity",
|
|
134
|
+
data=activity,
|
|
135
|
+
)
|
|
136
|
+
if activity["name"] == "start":
|
|
137
|
+
return map_call_params(activity["parameters"])
|
|
111
138
|
else:
|
|
112
|
-
logger.warning("
|
|
139
|
+
logger.warning("audiocodes_stream.unknown_message", data=data)
|
|
113
140
|
return None
|
|
114
141
|
|
|
115
142
|
def map_input_message(
|
|
@@ -121,14 +148,15 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
121
148
|
if data["type"] == "activities":
|
|
122
149
|
activities = data["activities"]
|
|
123
150
|
for activity in activities:
|
|
124
|
-
logger.debug("
|
|
151
|
+
logger.debug("audiocodes_stream.activity", data=activity)
|
|
125
152
|
if activity["name"] == "start":
|
|
153
|
+
# already handled in collect_call_parameters
|
|
126
154
|
pass
|
|
127
155
|
elif activity["name"] == "dtmf":
|
|
128
156
|
# TODO: handle DTMF input
|
|
129
157
|
pass
|
|
130
158
|
elif activity["name"] == "playFinished":
|
|
131
|
-
logger.debug("
|
|
159
|
+
logger.debug("audiocodes_stream.playFinished", data=activity)
|
|
132
160
|
if call_state.should_hangup:
|
|
133
161
|
logger.info("audiocodes.hangup")
|
|
134
162
|
self._send_hangup(ws, data)
|
|
@@ -136,37 +164,37 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
136
164
|
# we receive a end message from audiocodes
|
|
137
165
|
pass
|
|
138
166
|
else:
|
|
139
|
-
logger.warning("
|
|
167
|
+
logger.warning("audiocodes_stream.unknown_activity", data=activity)
|
|
140
168
|
elif data["type"] == "userStream.start":
|
|
141
|
-
logger.debug("
|
|
169
|
+
logger.debug("audiocodes_stream.userStream.start", data=data)
|
|
142
170
|
self._send_recognition_started(ws, data)
|
|
143
171
|
elif data["type"] == "userStream.chunk":
|
|
144
172
|
audio_bytes = self.channel_bytes_to_rasa_audio_bytes(data["audioChunk"])
|
|
145
173
|
return NewAudioAction(audio_bytes)
|
|
146
174
|
elif data["type"] == "userStream.stop":
|
|
147
|
-
logger.debug("
|
|
175
|
+
logger.debug("audiocodes_stream.stop_recognition", data=data)
|
|
148
176
|
self._send_recognition_ended(ws, data)
|
|
149
177
|
elif data["type"] == "session.resume":
|
|
150
|
-
logger.debug("
|
|
178
|
+
logger.debug("audiocodes_stream.resume", data=data)
|
|
151
179
|
self._send_accepted(ws, data)
|
|
152
180
|
elif data["type"] == "session.end":
|
|
153
|
-
logger.debug("
|
|
181
|
+
logger.debug("audiocodes_stream.end", data=data)
|
|
154
182
|
return EndConversationAction()
|
|
155
183
|
elif data["type"] == "connection.validate":
|
|
156
184
|
# not part of call flow; only sent when integration is created
|
|
157
185
|
self._send_validated(ws, data)
|
|
158
186
|
else:
|
|
159
|
-
logger.warning("
|
|
187
|
+
logger.warning("audiocodes_stream.unknown_message", data=data)
|
|
160
188
|
|
|
161
189
|
return ContinueConversationAction()
|
|
162
190
|
|
|
163
191
|
def _send_accepted(self, ws: Websocket, data: Dict[Text, Any]) -> None:
|
|
164
192
|
supported_formats = data.get("supportedMediaFormats", [])
|
|
165
|
-
preferred_format =
|
|
193
|
+
preferred_format = PREFERRED_AUDIO_FORMAT
|
|
166
194
|
|
|
167
195
|
if preferred_format not in supported_formats:
|
|
168
196
|
logger.warning(
|
|
169
|
-
"
|
|
197
|
+
"audiocodes_stream.format_not_supported",
|
|
170
198
|
supported_formats=supported_formats,
|
|
171
199
|
preferred_format=preferred_format,
|
|
172
200
|
)
|
|
@@ -174,7 +202,7 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
174
202
|
|
|
175
203
|
payload = {
|
|
176
204
|
"type": "session.accepted",
|
|
177
|
-
"mediaFormat":
|
|
205
|
+
"mediaFormat": PREFERRED_AUDIO_FORMAT,
|
|
178
206
|
}
|
|
179
207
|
_schedule_async_task(ws.send(json.dumps(payload)))
|
|
180
208
|
|
|
@@ -230,7 +258,7 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
230
258
|
self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
|
|
231
259
|
) -> Blueprint:
|
|
232
260
|
"""Defines a Sanic bluelogger.debug."""
|
|
233
|
-
blueprint = Blueprint("
|
|
261
|
+
blueprint = Blueprint("audiocodes_stream", __name__)
|
|
234
262
|
|
|
235
263
|
@blueprint.route("/", methods=["GET"])
|
|
236
264
|
async def health(_: Request) -> HTTPResponse:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from contextvars import ContextVar
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
5
|
|
|
6
6
|
from werkzeug.local import LocalProxy
|
|
7
7
|
|
|
@@ -19,14 +19,8 @@ class CallState:
|
|
|
19
19
|
should_hangup: bool = False
|
|
20
20
|
connection_failed: bool = False
|
|
21
21
|
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
client_sequence_number: int = 0
|
|
25
|
-
server_sequence_number: int = 0
|
|
26
|
-
audio_buffer: bytearray = field(default_factory=bytearray)
|
|
27
|
-
|
|
28
|
-
# Audiocodes requires a stream ID at start and end of stream
|
|
29
|
-
stream_id: int = 0
|
|
22
|
+
# Generic field for channel-specific state data
|
|
23
|
+
channel_data: Dict[str, Any] = field(default_factory=dict)
|
|
30
24
|
|
|
31
25
|
|
|
32
26
|
_call_state: ContextVar[CallState] = ContextVar("call_state")
|
|
@@ -27,8 +27,23 @@ from rasa.core.channels.voice_stream.voice_channel import (
|
|
|
27
27
|
VoiceOutputChannel,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
"""
|
|
31
|
+
Genesys throws a rate limit error with too many audio messages.
|
|
32
|
+
To avoid this, we buffer the audio messages and send them in chunks.
|
|
33
|
+
|
|
34
|
+
- global.inbound.binary.average.rate.per.second: 5
|
|
35
|
+
The allowed average rate per second of inbound binary data
|
|
36
|
+
|
|
37
|
+
- global.inbound.binary.max: 25
|
|
38
|
+
The maximum number of inbound binary data messages
|
|
39
|
+
that can be sent instantaneously
|
|
40
|
+
|
|
41
|
+
https://developer.genesys.cloud/organization/organization/limits#audiohook
|
|
42
|
+
|
|
43
|
+
The maximum binary message size is not mentioned
|
|
44
|
+
in the documentation but observed in their example app
|
|
45
|
+
https://github.com/GenesysCloudBlueprints/audioconnector-server-reference-implementation
|
|
46
|
+
"""
|
|
32
47
|
MAXIMUM_BINARY_MESSAGE_SIZE = 64000 # 64KB
|
|
33
48
|
logger = structlog.get_logger(__name__)
|
|
34
49
|
|
|
@@ -56,52 +71,7 @@ class GenesysOutputChannel(VoiceOutputChannel):
|
|
|
56
71
|
async def send_audio_bytes(
|
|
57
72
|
self, recipient_id: str, audio_bytes: RasaAudioBytes
|
|
58
73
|
) -> None:
|
|
59
|
-
|
|
60
|
-
Send audio bytes to the recipient with buffering.
|
|
61
|
-
|
|
62
|
-
Genesys throws a rate limit error with too many audio messages.
|
|
63
|
-
To avoid this, we buffer the audio messages and send them in chunks.
|
|
64
|
-
|
|
65
|
-
- global.inbound.binary.average.rate.per.second: 5
|
|
66
|
-
The allowed average rate per second of inbound binary data
|
|
67
|
-
|
|
68
|
-
- global.inbound.binary.max: 25
|
|
69
|
-
The maximum number of inbound binary data messages
|
|
70
|
-
that can be sent instantaneously
|
|
71
|
-
|
|
72
|
-
https://developer.genesys.cloud/organization/organization/limits#audiohook
|
|
73
|
-
"""
|
|
74
|
-
call_state.audio_buffer.extend(audio_bytes)
|
|
75
|
-
|
|
76
|
-
# If we receive a non-standard chunk size, assume it's the end of a sequence
|
|
77
|
-
# or buffer is more than 32KB (this is half of genesys's max audio message size)
|
|
78
|
-
if len(audio_bytes) != 1024 or len(call_state.audio_buffer) >= (
|
|
79
|
-
MAXIMUM_BINARY_MESSAGE_SIZE / 2
|
|
80
|
-
):
|
|
81
|
-
# TODO: we should send the buffer when we receive a synthesis complete event
|
|
82
|
-
# from TTS. This will ensure that the last audio chunk is always sent.
|
|
83
|
-
await self._send_audio_buffer(self.voice_websocket)
|
|
84
|
-
|
|
85
|
-
async def _send_audio_buffer(self, ws: Websocket) -> None:
|
|
86
|
-
"""Send the audio buffer to the recipient if it's not empty."""
|
|
87
|
-
if call_state.audio_buffer:
|
|
88
|
-
buffer_bytes = bytes(call_state.audio_buffer)
|
|
89
|
-
await self._send_bytes_to_ws(ws, buffer_bytes)
|
|
90
|
-
call_state.audio_buffer.clear()
|
|
91
|
-
|
|
92
|
-
async def _send_bytes_to_ws(self, ws: Websocket, data: bytes) -> None:
|
|
93
|
-
"""Send audio bytes to the recipient as a binary websocket message."""
|
|
94
|
-
if len(data) <= MAXIMUM_BINARY_MESSAGE_SIZE:
|
|
95
|
-
await self.voice_websocket.send(data)
|
|
96
|
-
else:
|
|
97
|
-
# split the audio into chunks
|
|
98
|
-
current_position = 0
|
|
99
|
-
while current_position < len(data):
|
|
100
|
-
end_position = min(
|
|
101
|
-
current_position + MAXIMUM_BINARY_MESSAGE_SIZE, len(data)
|
|
102
|
-
)
|
|
103
|
-
await self.voice_websocket.send(data[current_position:end_position])
|
|
104
|
-
current_position = end_position
|
|
74
|
+
await self.voice_websocket.send(audio_bytes)
|
|
105
75
|
|
|
106
76
|
async def send_marker_message(self, recipient_id: str) -> None:
|
|
107
77
|
"""
|
|
@@ -119,6 +89,17 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
119
89
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
120
90
|
super().__init__(*args, **kwargs)
|
|
121
91
|
|
|
92
|
+
def _ensure_channel_data_initialized(self) -> None:
|
|
93
|
+
"""Initialize Genesys-specific channel data if not already present.
|
|
94
|
+
|
|
95
|
+
Genesys requires the server and client each maintain a
|
|
96
|
+
monotonically increasing message sequence number.
|
|
97
|
+
"""
|
|
98
|
+
if "server_sequence_number" not in call_state.channel_data:
|
|
99
|
+
call_state.channel_data["server_sequence_number"] = 0
|
|
100
|
+
if "client_sequence_number" not in call_state.channel_data:
|
|
101
|
+
call_state.channel_data["client_sequence_number"] = 0
|
|
102
|
+
|
|
122
103
|
def _get_next_sequence(self) -> int:
|
|
123
104
|
"""
|
|
124
105
|
Get the next message sequence number
|
|
@@ -128,23 +109,26 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
128
109
|
Genesys requires the server and client each maintain a
|
|
129
110
|
monotonically increasing message sequence number.
|
|
130
111
|
"""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
112
|
+
self._ensure_channel_data_initialized()
|
|
113
|
+
call_state.channel_data["server_sequence_number"] += 1
|
|
114
|
+
return call_state.channel_data["server_sequence_number"]
|
|
134
115
|
|
|
135
116
|
def _get_last_client_sequence(self) -> int:
|
|
136
117
|
"""Get the last client(Genesys) sequence number."""
|
|
137
|
-
|
|
118
|
+
self._ensure_channel_data_initialized()
|
|
119
|
+
return call_state.channel_data["client_sequence_number"]
|
|
138
120
|
|
|
139
121
|
def _update_client_sequence(self, seq: int) -> None:
|
|
140
122
|
"""Update the client(Genesys) sequence number."""
|
|
141
|
-
|
|
123
|
+
self._ensure_channel_data_initialized()
|
|
124
|
+
|
|
125
|
+
if seq - call_state.channel_data["client_sequence_number"] != 1:
|
|
142
126
|
logger.warning(
|
|
143
127
|
"genesys.update_client_sequence.sequence_gap",
|
|
144
128
|
received_seq=seq,
|
|
145
|
-
last_seq=call_state.client_sequence_number,
|
|
129
|
+
last_seq=call_state.channel_data["client_sequence_number"],
|
|
146
130
|
)
|
|
147
|
-
call_state.client_sequence_number = seq
|
|
131
|
+
call_state.channel_data["client_sequence_number"] = seq
|
|
148
132
|
|
|
149
133
|
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
150
134
|
return RasaAudioBytes(input_bytes)
|
|
@@ -211,6 +195,7 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
211
195
|
voice_websocket,
|
|
212
196
|
tts_engine,
|
|
213
197
|
self.tts_cache,
|
|
198
|
+
min_buffer_size=MAXIMUM_BINARY_MESSAGE_SIZE // 2,
|
|
214
199
|
)
|
|
215
200
|
|
|
216
201
|
async def handle_open(self, ws: Websocket, message: dict) -> CallParameters:
|