rasa-pro 3.14.0.dev6__py3-none-any.whl → 3.14.0.dev7__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/exceptions.py +31 -1
- rasa/agents/protocol/a2a/a2a_agent.py +1 -1
- rasa/agents/protocol/mcp/mcp_base_agent.py +7 -6
- rasa/agents/protocol/mcp/mcp_task_agent.py +1 -1
- rasa/agents/utils.py +5 -0
- rasa/agents/validation.py +484 -0
- rasa/api.py +9 -6
- rasa/cli/arguments/train.py +2 -0
- rasa/cli/interactive.py +2 -0
- rasa/cli/train.py +2 -0
- rasa/cli/utils.py +85 -1
- rasa/core/actions/action.py +2 -6
- rasa/core/available_agents.py +47 -26
- rasa/core/channels/inspector/dist/assets/{arc-63212852.js → arc-cce7e0a8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-eecf6b13.js → blockDiagram-38ab4fdb-e2a49be7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-8f798a9a.js → c4Diagram-3d4e48cf-3def7895.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-858c2c20.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-df71a04c.js → classDiagram-70f12bd4-e66fe4df.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-9b275968.js → classDiagram-v2-f2320105-eb874aaa.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-4b80996c.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-1c669cad.js → createText-2e5e7dd3-cf934643.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-b1553799.js → edges-e0da2a9e-8fdf9155.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-112388d6.js → erDiagram-9861fffd-6106fb96.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-fdebec47.js → flowDb-956e92f1-4c2bb040.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-6280ede1.js → flowDiagram-66a62f08-f0ff96af.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-16f09b7a.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-e1dc03e5.js → flowchart-elk-definition-4a651766-a21707ec.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-83f68c51.js → ganttDiagram-c361ad54-c165acb1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-22f8666f.js → gitGraphDiagram-72cf32ee-b0564cf1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-ca9e6217.js → graph-e557e67a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-c5ceb692.js → index-3862675e-1ce60e9e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3e293924.js → index-996fe816.js} +173 -173
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-faa9999b.js → infoDiagram-f8f76790-893569e2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-c4dda8d9.js → journeyDiagram-49397b02-c29c864f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-d4307784.js → layout-649a5eae.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-0567aaa7.js → line-0e5685ed.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-c11b95cf.js → linear-eaa320bd.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-0c7d3ca9.js → mindmap-definition-fc14e90a-f35df9e6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-34b433fa.js → pieDiagram-8a3498a8-78339e96.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-4cab816e.js → quadrantDiagram-120e2f19-9b5f2f14.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-8c22fa9e.js → requirementDiagram-deff3bca-d05ddb3a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-70ce9e8e.js → sankeyDiagram-04a897e0-d9be5dfd.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-fbcd7fc9.js → sequenceDiagram-704730f1-0f1c4348.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-45f05ea6.js → stateDiagram-587899a1-9ddf63b3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-beab1ea6.js → stateDiagram-v2-d93cdb3a-bc2b81ed.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-2f29dbd5.js → styles-6aaf32cf-0a287936.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-951eac83.js → styles-9a916d00-e3941990.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-897fbfdd.js → styles-c10674c1-ce4eca24.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-d667fac1.js → svgDrawCommon-08f97a94-d822b1a8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-e3205144.js → timeline-definition-85554ec2-e144c7a7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-4abeb0e2.js → xychartDiagram-e933f94c-ab7f4e14.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/App.tsx +28 -4
- rasa/core/channels/inspector/src/components/DialogueAgentStack.tsx +108 -0
- rasa/core/channels/inspector/src/components/{DialogueStack.tsx → DialogueHistoryStack.tsx} +7 -8
- rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -0
- rasa/core/channels/inspector/src/helpers/utils.test.ts +127 -0
- rasa/core/channels/inspector/src/helpers/utils.ts +66 -1
- rasa/core/channels/inspector/src/types.ts +17 -0
- rasa/core/policies/flows/flow_executor.py +52 -31
- rasa/dialogue_understanding/commands/cancel_flow_command.py +2 -81
- rasa/dialogue_understanding/commands/start_flow_command.py +18 -113
- rasa/dialogue_understanding/commands/utils.py +118 -0
- rasa/dialogue_understanding/patterns/clarify.py +3 -14
- rasa/dialogue_understanding/patterns/continue_interrupted.py +185 -114
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +17 -23
- rasa/dialogue_understanding/stack/utils.py +43 -4
- rasa/dialogue_understanding/utils.py +24 -4
- rasa/model_training.py +8 -6
- rasa/shared/constants.py +3 -0
- rasa/shared/core/constants.py +5 -6
- rasa/shared/utils/health_check/health_check.py +7 -3
- rasa/shared/utils/mcp/server_connection.py +26 -6
- rasa/version.py +1 -1
- {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/METADATA +1 -1
- {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/RECORD +79 -76
- rasa/core/channels/inspector/dist/assets/channel-0cd70adf.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-a0f9c4ed.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-de9cc4aa.js +0 -1
- {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/NOTICE +0 -0
- {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/WHEEL +0 -0
- {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/entry_points.txt +0 -0
|
@@ -9,28 +9,30 @@ import { useEffect, useState, useCallback } from 'react'
|
|
|
9
9
|
import axios from 'axios'
|
|
10
10
|
import { useOurTheme } from './theme'
|
|
11
11
|
import { Welcome } from './components/Welcome'
|
|
12
|
-
import {
|
|
12
|
+
import { DialogueHistoryStack } from './components/DialogueHistoryStack.tsx'
|
|
13
13
|
import { DialougeInformation } from './components/DialogueInformation'
|
|
14
14
|
import { LoadingSpinner } from './components/LoadingSpinner'
|
|
15
15
|
import { DiagramFlow } from './components/DiagramFlow'
|
|
16
16
|
import { RecruitmentPanel } from './components/RecruitmentPanel'
|
|
17
17
|
import { formatSlots } from './helpers/formatters'
|
|
18
|
-
import {
|
|
18
|
+
import {Slot, Stack, Event, Flow, SelectedStack, Tracker, Agent} from './types'
|
|
19
19
|
import {
|
|
20
20
|
createHistoricalStack,
|
|
21
|
-
flowStepTrail,
|
|
21
|
+
flowStepTrail, updateAgentsStatus,
|
|
22
22
|
updatedActiveFrame,
|
|
23
23
|
} from './helpers/utils'
|
|
24
24
|
import queryString from 'query-string'
|
|
25
25
|
import { Chat } from './components/Chat'
|
|
26
26
|
import { LatencyDisplay } from './components/LatencyDisplay'
|
|
27
27
|
import useWebSocket, { ReadyState } from 'react-use-websocket'
|
|
28
|
+
import {DialogueAgentStack} from "./components/DialogueAgentStack.tsx";
|
|
28
29
|
|
|
29
30
|
export function App() {
|
|
30
31
|
const toast = useToast()
|
|
31
32
|
const { rasaSpace, rasaRadii } = useOurTheme()
|
|
32
33
|
const [rasaChatSessionId, setRasaChatSessionId] = useState<string>('')
|
|
33
34
|
const [flows, setFlows] = useState<Flow[]>([])
|
|
35
|
+
const [agents, setAgents] = useState<Agent[]>([])
|
|
34
36
|
const [slots, setSlots] = useState<Slot[]>([])
|
|
35
37
|
const [events, setEvents] = useState<Event[]>([])
|
|
36
38
|
const [story, setStory] = useState<string>('')
|
|
@@ -102,6 +104,23 @@ export function App() {
|
|
|
102
104
|
})
|
|
103
105
|
}, [toast])
|
|
104
106
|
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
axios
|
|
109
|
+
.get('/sub-agents', { params: { token } })
|
|
110
|
+
.then((response) => setAgents((response.data)))
|
|
111
|
+
.catch((error) => {
|
|
112
|
+
if (toast.isActive('agents')) return
|
|
113
|
+
toast({
|
|
114
|
+
id: 'agents',
|
|
115
|
+
title: 'Agents could not be retrieved',
|
|
116
|
+
description: error?.message || 'An unknown error happened.',
|
|
117
|
+
status: 'error',
|
|
118
|
+
duration: 4000,
|
|
119
|
+
isClosable: true,
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
}, [toast])
|
|
123
|
+
|
|
105
124
|
function fetchStory() {
|
|
106
125
|
axios
|
|
107
126
|
.get(`/conversations/${rasaChatSessionId}/story`, { params: { token } })
|
|
@@ -142,6 +161,7 @@ export function App() {
|
|
|
142
161
|
lastJsonMessage.stack,
|
|
143
162
|
lastJsonMessage.events,
|
|
144
163
|
)
|
|
164
|
+
setAgents(prevAgents => updateAgentsStatus(prevAgents, lastJsonMessage.events))
|
|
145
165
|
setStack(updatedStack)
|
|
146
166
|
setFrame(updatedActiveFrame(frame, updatedStack, lastJsonMessage.events))
|
|
147
167
|
setRasaChatSessionId(lastJsonMessage.sender_id)
|
|
@@ -240,12 +260,16 @@ export function App() {
|
|
|
240
260
|
{showRecruitmentPanel && (
|
|
241
261
|
<RecruitmentPanel onClose={handleCloseRecruitmentPanel} />
|
|
242
262
|
)}
|
|
243
|
-
<
|
|
263
|
+
<DialogueHistoryStack
|
|
244
264
|
sx={boxSx}
|
|
245
265
|
stack={stack}
|
|
246
266
|
active={frame?.stack}
|
|
247
267
|
onItemClick={onFrameSelected}
|
|
248
268
|
/>
|
|
269
|
+
{agents.length > 0 ? (<DialogueAgentStack
|
|
270
|
+
sx={boxSx}
|
|
271
|
+
agents={agents}
|
|
272
|
+
/>):null}
|
|
249
273
|
<DialougeInformation
|
|
250
274
|
sx={boxSx}
|
|
251
275
|
rasaChatSessionId={rasaChatSessionId}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
FlexProps,
|
|
4
|
+
Heading,
|
|
5
|
+
Table,
|
|
6
|
+
Thead,
|
|
7
|
+
Tbody,
|
|
8
|
+
Tr,
|
|
9
|
+
Th,
|
|
10
|
+
Td,
|
|
11
|
+
Text,
|
|
12
|
+
Flex,
|
|
13
|
+
} from '@chakra-ui/react'
|
|
14
|
+
import { useOurTheme } from '../theme'
|
|
15
|
+
import {Agent, Stack} from '../types'
|
|
16
|
+
|
|
17
|
+
function mapStatusToHumanReadableName(status: Agent['status']) {
|
|
18
|
+
switch (status) {
|
|
19
|
+
case 'running':
|
|
20
|
+
return 'Running'
|
|
21
|
+
case 'completed':
|
|
22
|
+
return 'Completed'
|
|
23
|
+
case 'interrupted':
|
|
24
|
+
return 'Interrupted'
|
|
25
|
+
case 'cancelled':
|
|
26
|
+
return 'Cancelled'
|
|
27
|
+
default:
|
|
28
|
+
return '-'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Props extends FlexProps {
|
|
33
|
+
agents: Agent[]
|
|
34
|
+
active?: Stack
|
|
35
|
+
onItemClick?: (stack: Stack) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function AgentRow({
|
|
39
|
+
agent,
|
|
40
|
+
}: {
|
|
41
|
+
agent: Agent
|
|
42
|
+
}) {
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Tr>
|
|
46
|
+
<Td>
|
|
47
|
+
<Text noOfLines={1}>{agent.name}</Text>
|
|
48
|
+
</Td>
|
|
49
|
+
<Td>
|
|
50
|
+
<Text noOfLines={1}>{mapStatusToHumanReadableName(agent.status)}</Text>
|
|
51
|
+
</Td>
|
|
52
|
+
</Tr>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const DialogueAgentStack = ({
|
|
57
|
+
sx,
|
|
58
|
+
agents,
|
|
59
|
+
active,
|
|
60
|
+
onItemClick,
|
|
61
|
+
...props
|
|
62
|
+
}: Props) => {
|
|
63
|
+
const { rasaSpace } = useOurTheme()
|
|
64
|
+
|
|
65
|
+
const containerSx = {
|
|
66
|
+
...sx,
|
|
67
|
+
pr: 0,
|
|
68
|
+
pb: 0,
|
|
69
|
+
flexDirection: 'column',
|
|
70
|
+
}
|
|
71
|
+
const overflowBox = {
|
|
72
|
+
height: '100%',
|
|
73
|
+
overflow: 'auto',
|
|
74
|
+
pr: rasaSpace[1],
|
|
75
|
+
pb: rasaSpace[0.5],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Flex sx={containerSx} {...props}>
|
|
80
|
+
<Flex>
|
|
81
|
+
<Heading size="lg" mb={rasaSpace[0.5]}>
|
|
82
|
+
Agents
|
|
83
|
+
</Heading>
|
|
84
|
+
<Text ml={rasaSpace[0.25]}>({agents.length} {agents.length === 1 ? 'agent' : 'agents'})</Text>
|
|
85
|
+
</Flex>
|
|
86
|
+
<Box sx={overflowBox}>
|
|
87
|
+
<Table width="100%" layout="fixed">
|
|
88
|
+
<Thead>
|
|
89
|
+
<Tr>
|
|
90
|
+
<Th>Name</Th>
|
|
91
|
+
<Th width="40%">Status</Th>
|
|
92
|
+
</Tr>
|
|
93
|
+
</Thead>
|
|
94
|
+
<Tbody>
|
|
95
|
+
{agents.length > 0 &&
|
|
96
|
+
[...agents]
|
|
97
|
+
.reverse()
|
|
98
|
+
.map((agent) => (
|
|
99
|
+
<AgentRow
|
|
100
|
+
agent={agent}
|
|
101
|
+
/>
|
|
102
|
+
))}
|
|
103
|
+
</Tbody>
|
|
104
|
+
</Table>
|
|
105
|
+
</Box>
|
|
106
|
+
</Flex>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -52,8 +52,6 @@ function StackRow({
|
|
|
52
52
|
},
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const agentOrStep = stack.agent_id ?? stack.step_id;
|
|
56
|
-
|
|
57
55
|
return (
|
|
58
56
|
<Tr
|
|
59
57
|
sx={
|
|
@@ -67,19 +65,19 @@ function StackRow({
|
|
|
67
65
|
</Tooltip>
|
|
68
66
|
</Td>
|
|
69
67
|
<Td>
|
|
70
|
-
{shouldShowTooltip(
|
|
71
|
-
<Tooltip label={
|
|
72
|
-
<Text noOfLines={1}>{
|
|
68
|
+
{shouldShowTooltip(stack.step_id) ? (
|
|
69
|
+
<Tooltip label={stack.step_id} hasArrow>
|
|
70
|
+
<Text noOfLines={1}>{stack.step_id}</Text>
|
|
73
71
|
</Tooltip>
|
|
74
72
|
) : (
|
|
75
|
-
<Text noOfLines={1}>{
|
|
73
|
+
<Text noOfLines={1}>{stack.step_id}</Text>
|
|
76
74
|
)}
|
|
77
75
|
</Td>
|
|
78
76
|
</Tr>
|
|
79
77
|
)
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
export const
|
|
80
|
+
export const DialogueHistoryStack = ({
|
|
83
81
|
sx,
|
|
84
82
|
stack,
|
|
85
83
|
active,
|
|
@@ -107,7 +105,7 @@ export const DialogueStack = ({
|
|
|
107
105
|
<Heading size="lg" mb={rasaSpace[0.5]}>
|
|
108
106
|
History
|
|
109
107
|
</Heading>
|
|
110
|
-
<Text ml={rasaSpace[0.25]}>({stack.length} flows)</Text>
|
|
108
|
+
<Text ml={rasaSpace[0.25]}>({stack.length} {stack.length === 1 ? 'flow' : 'flows'})</Text>
|
|
111
109
|
</Flex>
|
|
112
110
|
<Box sx={overflowBox}>
|
|
113
111
|
<Table width="100%" layout="fixed">
|
|
@@ -136,6 +134,7 @@ export const DialogueStack = ({
|
|
|
136
134
|
flow_id: '-',
|
|
137
135
|
step_id: '-',
|
|
138
136
|
ended: false,
|
|
137
|
+
type: "flow"
|
|
139
138
|
}}
|
|
140
139
|
/>
|
|
141
140
|
)}
|
|
@@ -294,6 +294,7 @@ describe('helpers', () => {
|
|
|
294
294
|
step_id: 'step_id',
|
|
295
295
|
collect: fieldValue,
|
|
296
296
|
ended: false,
|
|
297
|
+
type: 'flow',
|
|
297
298
|
}),
|
|
298
299
|
).toEqual(fieldValue)
|
|
299
300
|
})
|
|
@@ -307,6 +308,7 @@ describe('helpers', () => {
|
|
|
307
308
|
flow_id: 'flow_id',
|
|
308
309
|
step_id: 'step_id',
|
|
309
310
|
ended: false,
|
|
311
|
+
type: 'flow',
|
|
310
312
|
}),
|
|
311
313
|
).toEqual(fieldValue)
|
|
312
314
|
})
|
|
@@ -321,6 +323,7 @@ describe('helpers', () => {
|
|
|
321
323
|
step_id: 'step_id',
|
|
322
324
|
collect: fieldValue,
|
|
323
325
|
ended: false,
|
|
326
|
+
type: 'flow',
|
|
324
327
|
}),
|
|
325
328
|
).toEqual(`${fieldValue} is not null`)
|
|
326
329
|
})
|
|
@@ -335,6 +338,7 @@ describe('helpers', () => {
|
|
|
335
338
|
step_id: 'step_id',
|
|
336
339
|
collect: fieldValue,
|
|
337
340
|
ended: false,
|
|
341
|
+
type: 'flow',
|
|
338
342
|
}),
|
|
339
343
|
).toEqual(`not ${fieldValue}`)
|
|
340
344
|
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { updateAgentsStatus } from './utils'
|
|
2
|
+
import type { Agent, Event } from '../types'
|
|
3
|
+
|
|
4
|
+
// Helper to clone agents (immutability check)
|
|
5
|
+
const clone = <T>(x: T): T => JSON.parse(JSON.stringify(x))
|
|
6
|
+
|
|
7
|
+
describe('updateAgentsStatus', () => {
|
|
8
|
+
test('returns empty array when agents undefined', () => {
|
|
9
|
+
// @ts-expect-error testing runtime behavior with undefined
|
|
10
|
+
expect(updateAgentsStatus(undefined, [])).toEqual([])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('returns same agents when agents empty', () => {
|
|
14
|
+
const agents: Agent[] = []
|
|
15
|
+
const events: Event[] = [
|
|
16
|
+
{ event: 'agent_started', timestamp: '1' } as any,
|
|
17
|
+
]
|
|
18
|
+
expect(updateAgentsStatus(agents, events)).toEqual([])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('returns same agents when events empty', () => {
|
|
22
|
+
const agents: Agent[] = [
|
|
23
|
+
{ name: 'alpha', status: 'completed' },
|
|
24
|
+
]
|
|
25
|
+
const events: Event[] = []
|
|
26
|
+
expect(updateAgentsStatus(clone(agents), events)).toEqual(agents)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('ignores non-agent events', () => {
|
|
30
|
+
const agents: Agent[] = [
|
|
31
|
+
{ name: 'alpha', status: 'completed' },
|
|
32
|
+
]
|
|
33
|
+
const events: Event[] = [
|
|
34
|
+
{ event: 'user', text: 'hi', timestamp: '1' } as any,
|
|
35
|
+
{ event: 'bot', text: 'hey', timestamp: '2' } as any,
|
|
36
|
+
]
|
|
37
|
+
expect(updateAgentsStatus(clone(agents), events)).toEqual(agents)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('uses latest event by timestamp per agent', () => {
|
|
41
|
+
const agents: Agent[] = [
|
|
42
|
+
{ name: 'alpha', status: 'completed' },
|
|
43
|
+
{ name: 'beta', status: 'completed' },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
const events: Event[] = [
|
|
47
|
+
{ event: 'agent_started', agent_id: 'alpha', timestamp: 1 } as any,
|
|
48
|
+
{ event: 'agent_interrupted', agent_id: 'alpha', timestamp: 2 } as any,
|
|
49
|
+
{ event: 'agent_resumed', agent_id: 'alpha', timestamp: 3 } as any,
|
|
50
|
+
{ event: 'agent_cancelled', agent_id: 'beta', timestamp: 5 } as any,
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
const result = updateAgentsStatus(clone(agents), events)
|
|
54
|
+
expect(result).toEqual([
|
|
55
|
+
{ name: 'alpha', status: 'running' },
|
|
56
|
+
{ name: 'beta', status: 'cancelled' },
|
|
57
|
+
])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('later event wins when timestamps missing', () => {
|
|
61
|
+
const agents: Agent[] = [
|
|
62
|
+
{ name: 'alpha', status: 'completed' },
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
const events: Event[] = [
|
|
66
|
+
{ event: 'agent_started', agent_id: 'alpha', timestamp: null } as any,
|
|
67
|
+
{ event: 'agent_interrupted', agent_id: 'alpha', timestamp: null } as any,
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
const result = updateAgentsStatus(clone(agents), events)
|
|
71
|
+
expect(result).toEqual([
|
|
72
|
+
{ name: 'alpha', status: 'interrupted' },
|
|
73
|
+
])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('agent_completed maps to provided status and falls back to completed', () => {
|
|
77
|
+
const agents: Agent[] = [
|
|
78
|
+
{ name: 'alpha', status: 'running' },
|
|
79
|
+
{ name: 'beta', status: 'running' },
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
const events: Event[] = [
|
|
83
|
+
{ event: 'agent_completed', agent_id: 'alpha', status: 'completed', timestamp: 10 } as any,
|
|
84
|
+
{ event: 'agent_completed', agent_id: 'beta', timestamp: 10 } as any,
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
const result = updateAgentsStatus(clone(agents), events)
|
|
88
|
+
expect(result).toEqual([
|
|
89
|
+
{ name: 'alpha', status: 'completed' },
|
|
90
|
+
{ name: 'beta', status: 'completed' },
|
|
91
|
+
])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('unmatched agents remain unchanged', () => {
|
|
95
|
+
const agents: Agent[] = [
|
|
96
|
+
{ name: 'alpha', status: 'running' },
|
|
97
|
+
{ name: 'gamma', status: 'interrupted' },
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
const events: Event[] = [
|
|
101
|
+
{ event: 'agent_resumed', agent_id: 'alpha', timestamp: 1 } as any,
|
|
102
|
+
{ event: 'agent_cancelled', agent_id: 'beta', timestamp: 2 } as any, // beta not in agents list
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
const result = updateAgentsStatus(clone(agents), events)
|
|
106
|
+
expect(result).toEqual([
|
|
107
|
+
{ name: 'alpha', status: 'running' },
|
|
108
|
+
{ name: 'gamma', status: 'interrupted' },
|
|
109
|
+
])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('handles mixed timestamp types (string/number)', () => {
|
|
113
|
+
const agents: Agent[] = [
|
|
114
|
+
{ name: 'alpha', status: 'completed' },
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
const events: Event[] = [
|
|
118
|
+
{ event: 'agent_started', agent_id: 'alpha', timestamp: '1' } as any,
|
|
119
|
+
{ event: 'agent_interrupted', agent_id: 'alpha', timestamp: 2 } as any,
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
const result = updateAgentsStatus(clone(agents), events)
|
|
123
|
+
expect(result).toEqual([
|
|
124
|
+
{ name: 'alpha', status: 'interrupted' },
|
|
125
|
+
])
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {SelectedStack, Stack, Event, Agent, AGENT_EVENT_TYPES} from '../types'
|
|
2
2
|
import { immutableJSONPatch } from 'immutable-json-patch'
|
|
3
3
|
|
|
4
4
|
export const shouldShowTooltip = (text: string) => {
|
|
@@ -146,3 +146,68 @@ export const updatedActiveFrame = (
|
|
|
146
146
|
return previous
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
export function updateAgentsStatus(agents: Agent[], events: Event[]): Agent[] {
|
|
151
|
+
if (!agents || agents.length === 0 || !events || events.length === 0) {
|
|
152
|
+
return agents || []
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const agentEventTypes = new Set<Event['event']>(AGENT_EVENT_TYPES as unknown as Event['event'][] )
|
|
156
|
+
|
|
157
|
+
type AgentEventLite = Agent & {
|
|
158
|
+
agent_id: string
|
|
159
|
+
event: Event['event']
|
|
160
|
+
timestamp?: string | number | null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const latestByAgent = new Map<string, AgentEventLite>()
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < events.length; i++) {
|
|
166
|
+
const event = events[i] as unknown as AgentEventLite
|
|
167
|
+
if (!event || !agentEventTypes.has(event.event)) continue
|
|
168
|
+
|
|
169
|
+
const agentId = event.agent_id
|
|
170
|
+
if (!agentId) continue
|
|
171
|
+
|
|
172
|
+
const previousEvent = latestByAgent.get(agentId)
|
|
173
|
+
|
|
174
|
+
const currentTimestamp = event.timestamp == null ? undefined : Number(event.timestamp)
|
|
175
|
+
const previousTimestamp = previousEvent?.timestamp == null ? undefined : Number(previousEvent.timestamp)
|
|
176
|
+
|
|
177
|
+
const isNewer =
|
|
178
|
+
previousEvent == null ||
|
|
179
|
+
(currentTimestamp != null && previousTimestamp != null && currentTimestamp > previousTimestamp) ||
|
|
180
|
+
(currentTimestamp != null && previousTimestamp == null)
|
|
181
|
+
// If both timestamps are missing, prefer later in the array (current)
|
|
182
|
+
|
|
183
|
+
if (isNewer || (currentTimestamp == null && previousTimestamp == null)) {
|
|
184
|
+
latestByAgent.set(agentId, event)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return agents.map((agent) => {
|
|
189
|
+
const latest = latestByAgent.get(agent.name)
|
|
190
|
+
if (!latest) return agent
|
|
191
|
+
|
|
192
|
+
let status = agent.status
|
|
193
|
+
switch (latest.event) {
|
|
194
|
+
case 'agent_started':
|
|
195
|
+
case 'agent_resumed':
|
|
196
|
+
status = 'running'
|
|
197
|
+
break
|
|
198
|
+
case 'agent_interrupted':
|
|
199
|
+
status = 'interrupted'
|
|
200
|
+
break
|
|
201
|
+
case 'agent_cancelled':
|
|
202
|
+
status = 'cancelled'
|
|
203
|
+
break
|
|
204
|
+
case 'agent_completed':
|
|
205
|
+
status = latest.status || 'completed'
|
|
206
|
+
break
|
|
207
|
+
default:
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { ...agent, status }
|
|
212
|
+
})
|
|
213
|
+
}
|
|
@@ -4,6 +4,16 @@ export interface Slot {
|
|
|
4
4
|
value: any
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export const AGENT_EVENT_TYPES = [
|
|
8
|
+
'agent_started',
|
|
9
|
+
'agent_completed',
|
|
10
|
+
'agent_interrupted',
|
|
11
|
+
'agent_resumed',
|
|
12
|
+
'agent_cancelled',
|
|
13
|
+
] as const
|
|
14
|
+
|
|
15
|
+
export type AgentEvents = typeof AGENT_EVENT_TYPES[number];
|
|
16
|
+
|
|
7
17
|
export interface Event {
|
|
8
18
|
event:
|
|
9
19
|
| 'user'
|
|
@@ -13,6 +23,7 @@ export interface Event {
|
|
|
13
23
|
| 'stack'
|
|
14
24
|
| 'restart'
|
|
15
25
|
| 'session_ended'
|
|
26
|
+
| AgentEvents
|
|
16
27
|
text?: string
|
|
17
28
|
timestamp: string
|
|
18
29
|
update?: string
|
|
@@ -40,6 +51,7 @@ export interface Stack {
|
|
|
40
51
|
collect?: string
|
|
41
52
|
utter?: string
|
|
42
53
|
ended: boolean
|
|
54
|
+
type: "flow" | "agent"
|
|
43
55
|
agent_id?: string
|
|
44
56
|
state?: "waiting_for_input" | "interrupted"
|
|
45
57
|
}
|
|
@@ -66,6 +78,11 @@ export interface Flow {
|
|
|
66
78
|
steps: Step[]
|
|
67
79
|
}
|
|
68
80
|
|
|
81
|
+
export interface Agent {
|
|
82
|
+
name: string
|
|
83
|
+
status: "running" | "completed" | "interrupted" | "cancelled"
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
interface NextStepThen {
|
|
70
87
|
action: string
|
|
71
88
|
id: string
|
|
@@ -65,7 +65,10 @@ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
|
|
|
65
65
|
AgentState,
|
|
66
66
|
FlowStackFrameType,
|
|
67
67
|
)
|
|
68
|
-
from rasa.dialogue_understanding.stack.utils import
|
|
68
|
+
from rasa.dialogue_understanding.stack.utils import (
|
|
69
|
+
user_frames_on_the_stack,
|
|
70
|
+
)
|
|
71
|
+
from rasa.dialogue_understanding.utils import assemble_options_string
|
|
69
72
|
from rasa.shared.agents.utils import get_protocol_type
|
|
70
73
|
from rasa.shared.constants import RASA_PATTERN_HUMAN_HANDOFF
|
|
71
74
|
from rasa.shared.core.constants import (
|
|
@@ -263,38 +266,58 @@ def trigger_pattern_continue_interrupted(
|
|
|
263
266
|
stack: DialogueStack,
|
|
264
267
|
flows: FlowsList,
|
|
265
268
|
tracker: DialogueStateTracker,
|
|
266
|
-
) ->
|
|
269
|
+
) -> None:
|
|
267
270
|
"""Trigger the pattern to continue an interrupted flow if needed."""
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# get previously started user flow that will be continued
|
|
271
|
-
interrupted_user_flow_frame = top_user_flow_frame(stack)
|
|
272
|
-
interrupted_user_flow_step = (
|
|
273
|
-
interrupted_user_flow_frame.step(flows) if interrupted_user_flow_frame else None
|
|
274
|
-
)
|
|
275
|
-
interrupted_user_flow = (
|
|
276
|
-
interrupted_user_flow_frame.flow(flows) if interrupted_user_flow_frame else None
|
|
277
|
-
)
|
|
278
|
-
|
|
271
|
+
# only trigger the pattern if the current frame is a user flow frame
|
|
272
|
+
# with a frame type of interrupt
|
|
279
273
|
if (
|
|
280
|
-
isinstance(current_frame, UserFlowStackFrame)
|
|
281
|
-
|
|
282
|
-
and interrupted_user_flow is not None
|
|
283
|
-
and current_frame.frame_type == FlowStackFrameType.INTERRUPT
|
|
284
|
-
and not is_step_end_of_flow(interrupted_user_flow_step)
|
|
274
|
+
not isinstance(current_frame, UserFlowStackFrame)
|
|
275
|
+
or current_frame.frame_type != FlowStackFrameType.INTERRUPT
|
|
285
276
|
):
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
# get all previously interrupted user flows
|
|
280
|
+
interrupted_user_flow_stack_frames = user_frames_on_the_stack(stack)
|
|
281
|
+
|
|
282
|
+
interrupted_user_flows_to_continue: List[UserFlowStackFrame] = []
|
|
283
|
+
# check if interrupted user flows can be continued
|
|
284
|
+
# i.e. the flow is not at the end of the flow
|
|
285
|
+
for frame in interrupted_user_flow_stack_frames:
|
|
286
|
+
interrupted_user_flow_step = frame.step(flows)
|
|
287
|
+
interrupted_user_flow = frame.flow(flows)
|
|
288
|
+
if (
|
|
289
|
+
interrupted_user_flow_step is not None
|
|
290
|
+
and interrupted_user_flow is not None
|
|
291
|
+
and not is_step_end_of_flow(interrupted_user_flow_step)
|
|
292
|
+
):
|
|
293
|
+
interrupted_user_flows_to_continue.append(frame)
|
|
294
|
+
|
|
295
|
+
# if there are no interrupted user flows to continue,
|
|
296
|
+
# we don't need to trigger the pattern
|
|
297
|
+
if len(interrupted_user_flows_to_continue) == 0:
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
# get the flow names and ids of the interrupted flows
|
|
301
|
+
# and assemble the options string
|
|
302
|
+
flow_names: List[str] = []
|
|
303
|
+
flow_ids: List[str] = []
|
|
304
|
+
for frame in interrupted_user_flows_to_continue:
|
|
305
|
+
flow_names.append(
|
|
306
|
+
frame.flow(flows).readable_name(language=tracker.current_language)
|
|
292
307
|
)
|
|
293
|
-
|
|
294
|
-
|
|
308
|
+
flow_ids.append(frame.flow_id)
|
|
309
|
+
options_string = assemble_options_string(flow_names)
|
|
310
|
+
|
|
311
|
+
# trigger the pattern to continue the interrupted flows
|
|
312
|
+
stack.push(
|
|
313
|
+
ContinueInterruptedPatternFlowStackFrame(
|
|
314
|
+
interrupted_flow_names=flow_names,
|
|
315
|
+
interrupted_flow_ids=flow_ids,
|
|
316
|
+
interrupted_flow_options=options_string,
|
|
295
317
|
)
|
|
318
|
+
)
|
|
296
319
|
|
|
297
|
-
return
|
|
320
|
+
return None
|
|
298
321
|
|
|
299
322
|
|
|
300
323
|
def trigger_pattern_completed(
|
|
@@ -707,12 +730,10 @@ def _run_end_step(
|
|
|
707
730
|
structlogger.debug("flow.step.run.flow_end")
|
|
708
731
|
current_frame = stack.pop()
|
|
709
732
|
trigger_pattern_completed(current_frame, stack, flows)
|
|
710
|
-
|
|
711
|
-
current_frame, stack, flows, tracker
|
|
712
|
-
)
|
|
733
|
+
trigger_pattern_continue_interrupted(current_frame, stack, flows, tracker)
|
|
713
734
|
reset_events: List[Event] = reset_scoped_slots(current_frame, flow, tracker)
|
|
714
735
|
return ContinueFlowWithNextStep(
|
|
715
|
-
events=initial_events + reset_events
|
|
736
|
+
events=initial_events + reset_events, has_flow_ended=True
|
|
716
737
|
)
|
|
717
738
|
|
|
718
739
|
|