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.

Files changed (82) hide show
  1. rasa/agents/exceptions.py +31 -1
  2. rasa/agents/protocol/a2a/a2a_agent.py +1 -1
  3. rasa/agents/protocol/mcp/mcp_base_agent.py +7 -6
  4. rasa/agents/protocol/mcp/mcp_task_agent.py +1 -1
  5. rasa/agents/utils.py +5 -0
  6. rasa/agents/validation.py +484 -0
  7. rasa/api.py +9 -6
  8. rasa/cli/arguments/train.py +2 -0
  9. rasa/cli/interactive.py +2 -0
  10. rasa/cli/train.py +2 -0
  11. rasa/cli/utils.py +85 -1
  12. rasa/core/actions/action.py +2 -6
  13. rasa/core/available_agents.py +47 -26
  14. rasa/core/channels/inspector/dist/assets/{arc-63212852.js → arc-cce7e0a8.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-eecf6b13.js → blockDiagram-38ab4fdb-e2a49be7.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-8f798a9a.js → c4Diagram-3d4e48cf-3def7895.js} +1 -1
  17. rasa/core/channels/inspector/dist/assets/channel-858c2c20.js +1 -0
  18. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-df71a04c.js → classDiagram-70f12bd4-e66fe4df.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-9b275968.js → classDiagram-v2-f2320105-eb874aaa.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/clone-4b80996c.js +1 -0
  21. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-1c669cad.js → createText-2e5e7dd3-cf934643.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-b1553799.js → edges-e0da2a9e-8fdf9155.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-112388d6.js → erDiagram-9861fffd-6106fb96.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-fdebec47.js → flowDb-956e92f1-4c2bb040.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-6280ede1.js → flowDiagram-66a62f08-f0ff96af.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-16f09b7a.js +1 -0
  27. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-e1dc03e5.js → flowchart-elk-definition-4a651766-a21707ec.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-83f68c51.js → ganttDiagram-c361ad54-c165acb1.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-22f8666f.js → gitGraphDiagram-72cf32ee-b0564cf1.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{graph-ca9e6217.js → graph-e557e67a.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{index-3862675e-c5ceb692.js → index-3862675e-1ce60e9e.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{index-3e293924.js → index-996fe816.js} +173 -173
  33. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-faa9999b.js → infoDiagram-f8f76790-893569e2.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-c4dda8d9.js → journeyDiagram-49397b02-c29c864f.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{layout-d4307784.js → layout-649a5eae.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{line-0567aaa7.js → line-0e5685ed.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{linear-c11b95cf.js → linear-eaa320bd.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-0c7d3ca9.js → mindmap-definition-fc14e90a-f35df9e6.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-34b433fa.js → pieDiagram-8a3498a8-78339e96.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-4cab816e.js → quadrantDiagram-120e2f19-9b5f2f14.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-8c22fa9e.js → requirementDiagram-deff3bca-d05ddb3a.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-70ce9e8e.js → sankeyDiagram-04a897e0-d9be5dfd.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-fbcd7fc9.js → sequenceDiagram-704730f1-0f1c4348.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-45f05ea6.js → stateDiagram-587899a1-9ddf63b3.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-beab1ea6.js → stateDiagram-v2-d93cdb3a-bc2b81ed.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-2f29dbd5.js → styles-6aaf32cf-0a287936.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-951eac83.js → styles-9a916d00-e3941990.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-897fbfdd.js → styles-c10674c1-ce4eca24.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-d667fac1.js → svgDrawCommon-08f97a94-d822b1a8.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-e3205144.js → timeline-definition-85554ec2-e144c7a7.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-4abeb0e2.js → xychartDiagram-e933f94c-ab7f4e14.js} +1 -1
  52. rasa/core/channels/inspector/dist/index.html +1 -1
  53. rasa/core/channels/inspector/src/App.tsx +28 -4
  54. rasa/core/channels/inspector/src/components/DialogueAgentStack.tsx +108 -0
  55. rasa/core/channels/inspector/src/components/{DialogueStack.tsx → DialogueHistoryStack.tsx} +7 -8
  56. rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -0
  57. rasa/core/channels/inspector/src/helpers/utils.test.ts +127 -0
  58. rasa/core/channels/inspector/src/helpers/utils.ts +66 -1
  59. rasa/core/channels/inspector/src/types.ts +17 -0
  60. rasa/core/policies/flows/flow_executor.py +52 -31
  61. rasa/dialogue_understanding/commands/cancel_flow_command.py +2 -81
  62. rasa/dialogue_understanding/commands/start_flow_command.py +18 -113
  63. rasa/dialogue_understanding/commands/utils.py +118 -0
  64. rasa/dialogue_understanding/patterns/clarify.py +3 -14
  65. rasa/dialogue_understanding/patterns/continue_interrupted.py +185 -114
  66. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +17 -23
  67. rasa/dialogue_understanding/stack/utils.py +43 -4
  68. rasa/dialogue_understanding/utils.py +24 -4
  69. rasa/model_training.py +8 -6
  70. rasa/shared/constants.py +3 -0
  71. rasa/shared/core/constants.py +5 -6
  72. rasa/shared/utils/health_check/health_check.py +7 -3
  73. rasa/shared/utils/mcp/server_connection.py +26 -6
  74. rasa/version.py +1 -1
  75. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/METADATA +1 -1
  76. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/RECORD +79 -76
  77. rasa/core/channels/inspector/dist/assets/channel-0cd70adf.js +0 -1
  78. rasa/core/channels/inspector/dist/assets/clone-a0f9c4ed.js +0 -1
  79. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-de9cc4aa.js +0 -1
  80. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/NOTICE +0 -0
  81. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/WHEEL +0 -0
  82. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/entry_points.txt +0 -0
@@ -1,31 +1,47 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass
4
- from typing import Any, Dict, List, Optional, Text
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Dict, List, Optional, Text, Tuple
5
+
6
+ import structlog
5
7
 
6
8
  from rasa.core.actions.action import Action
7
9
  from rasa.core.channels import OutputChannel
8
10
  from rasa.core.nlg import NaturalLanguageGenerator
11
+ from rasa.dialogue_understanding.commands.utils import (
12
+ resume_flow,
13
+ )
14
+ from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
15
+ from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
9
16
  from rasa.dialogue_understanding.stack.frames import PatternFlowStackFrame
10
- from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
17
+ from rasa.dialogue_understanding.stack.frames.dialogue_stack_frame import (
18
+ DialogueStackFrame,
19
+ )
20
+ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
21
+ AgentStackFrame,
22
+ FlowStackFrameType,
23
+ UserFlowStackFrame,
24
+ )
25
+ from rasa.dialogue_understanding.stack.utils import (
26
+ get_active_continue_interrupted_pattern_frame,
27
+ )
11
28
  from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
12
29
  from rasa.shared.core.constants import (
13
- ACTION_ASK_INTERRUPTED_FLOW_TO_CONTINUE,
14
- ACTION_CANCEL_INTERRUPTED_FLOW,
30
+ ACTION_CANCEL_INTERRUPTED_FLOWS,
15
31
  ACTION_CONTINUE_INTERRUPTED_FLOW,
16
- ACTION_SET_INTERRUPTED_FLOWS,
17
32
  )
18
33
  from rasa.shared.core.domain import Domain
19
- from rasa.shared.core.events import Event, SlotSet
34
+ from rasa.shared.core.events import AgentCancelled, Event, FlowCancelled, SlotSet
20
35
  from rasa.shared.core.trackers import DialogueStateTracker
21
36
 
22
37
  FLOW_PATTERN_CONTINUE_INTERRUPTED = (
23
38
  RASA_DEFAULT_FLOW_PATTERN_PREFIX + "continue_interrupted"
24
39
  )
25
- INTERRUPTED_FLOWS_SLOT = "interrupted_flows"
26
40
  INTERRUPTED_FLOW_TO_CONTINUE_SLOT = "interrupted_flow_to_continue"
27
- MULTIPLE_FLOWS_INTERRUPTED_SLOT = "multiple_flows_interrupted"
28
- CONFIRMATION_CONTINUE_INTERRUPTED_FLOW_SLOT = "confirmation_continue_interrupted_flow"
41
+ CONTINUE_INTERRUPTED_FLOW_CONFIRMATION_SLOT = "continue_interrupted_flow_confirmation"
42
+
43
+
44
+ structlogger = structlog.get_logger()
29
45
 
30
46
 
31
47
  @dataclass
@@ -34,8 +50,14 @@ class ContinueInterruptedPatternFlowStackFrame(PatternFlowStackFrame):
34
50
 
35
51
  flow_id: str = FLOW_PATTERN_CONTINUE_INTERRUPTED
36
52
  """The ID of the flow."""
37
- previous_flow_name: str = ""
38
- """The name of the flow that was interrupted."""
53
+ interrupted_flow_names: List[str] = field(default_factory=list)
54
+ """The names of the previous flows that were interrupted."""
55
+ interrupted_flow_ids: List[str] = field(default_factory=list)
56
+ """The ids of the previous flows that were interrupted."""
57
+ interrupted_flow_options: str = ""
58
+ """The options that the user can choose from as a string."""
59
+ multiple_flows_interrupted: bool = False
60
+ """Whether the user has interrupted multiple flows."""
39
61
 
40
62
  @classmethod
41
63
  def type(cls) -> str:
@@ -55,7 +77,10 @@ class ContinueInterruptedPatternFlowStackFrame(PatternFlowStackFrame):
55
77
  return ContinueInterruptedPatternFlowStackFrame(
56
78
  frame_id=data["frame_id"],
57
79
  step_id=data["step_id"],
58
- previous_flow_name=data["previous_flow_name"],
80
+ interrupted_flow_names=data["interrupted_flow_names"],
81
+ interrupted_flow_ids=data["interrupted_flow_ids"],
82
+ interrupted_flow_options=data["interrupted_flow_options"],
83
+ multiple_flows_interrupted=len(data["interrupted_flow_names"]) > 1,
59
84
  )
60
85
 
61
86
  def __eq__(self, other: Any) -> bool:
@@ -64,13 +89,15 @@ class ContinueInterruptedPatternFlowStackFrame(PatternFlowStackFrame):
64
89
  return (
65
90
  self.flow_id == other.flow_id
66
91
  and self.step_id == other.step_id
67
- and self.previous_flow_name == other.previous_flow_name
92
+ and self.interrupted_flow_names == other.interrupted_flow_names
93
+ and self.interrupted_flow_ids == other.interrupted_flow_ids
94
+ and self.interrupted_flow_options == other.interrupted_flow_options
68
95
  )
69
96
 
70
97
 
71
- class ActionSetInterruptedFlows(Action):
98
+ class ActionContinueInterruptedFlow(Action):
72
99
  def name(self) -> str:
73
- return ACTION_SET_INTERRUPTED_FLOWS
100
+ return ACTION_CONTINUE_INTERRUPTED_FLOW
74
101
 
75
102
  async def run(
76
103
  self,
@@ -79,25 +106,62 @@ class ActionSetInterruptedFlows(Action):
79
106
  tracker: DialogueStateTracker,
80
107
  domain: Domain,
81
108
  metadata: Optional[Dict[Text, Any]] = None,
82
- ) -> list[Event]:
83
- interrupted_flows_set = set()
84
- interrupted_user_flow_stack_frames = tracker.stack.get_all_user_flow_frames()
109
+ ) -> List[Event]:
110
+ # get the pattern frame from the stack
111
+ pattern_frame = get_active_continue_interrupted_pattern_frame(tracker.stack)
112
+
113
+ if pattern_frame is None:
114
+ structlogger.warning(
115
+ "action.continue_interrupted_flows.no_continue_interrupted_frame"
116
+ )
117
+ return []
85
118
 
86
- for frame in interrupted_user_flow_stack_frames:
87
- interrupted_flows_set.add(frame.flow_id)
119
+ interrupted_flow_ids = pattern_frame.interrupted_flow_ids
120
+ interrupted_flow_names = pattern_frame.interrupted_flow_names
121
+ multiple_flows_interrupted = pattern_frame.multiple_flows_interrupted
122
+
123
+ flow_to_continue = None
124
+ if not multiple_flows_interrupted:
125
+ # the user confirmed that they want to continue the flow
126
+ # as only one flow was interrupted, we can just continue the first one
127
+ flow_to_continue = interrupted_flow_ids[0]
128
+ else:
129
+ # the user mentioned the flow they want to continue
130
+ # check if the flow is in the list of interrupted flows
131
+ selected_flow = tracker.get_slot(INTERRUPTED_FLOW_TO_CONTINUE_SLOT)
132
+ if selected_flow in interrupted_flow_ids:
133
+ flow_to_continue = selected_flow
134
+ elif selected_flow in interrupted_flow_names:
135
+ # the user mentioned the flow by name
136
+ # find the flow id for the flow name
137
+ # the list of names and ids are in the same order
138
+ flow_to_continue = interrupted_flow_ids[
139
+ interrupted_flow_names.index(selected_flow)
140
+ ]
141
+
142
+ # if the user did not select a valid flow,
143
+ # we need to ask them to select a valid flow
144
+ if flow_to_continue is None:
145
+ await output_channel.send_text_message(
146
+ tracker.sender_id,
147
+ "You haven't selected a valid task to resume. "
148
+ "Please specify the task you would like to continue. "
149
+ "The options are: {{context.interrupted_flow_options}}",
150
+ )
151
+ return []
88
152
 
89
- interrupted_flows = list(interrupted_flows_set)
90
- multiple_flows_interrupted = len(interrupted_flows) > 1
153
+ # resume the flow the user selected
154
+ events = resume_flow(flow_to_continue, tracker, tracker.stack)
91
155
 
92
- return [
93
- SlotSet(INTERRUPTED_FLOWS_SLOT, interrupted_flows),
94
- SlotSet(MULTIPLE_FLOWS_INTERRUPTED_SLOT, multiple_flows_interrupted),
156
+ return events + [
157
+ SlotSet(INTERRUPTED_FLOW_TO_CONTINUE_SLOT, None),
158
+ SlotSet(CONTINUE_INTERRUPTED_FLOW_CONFIRMATION_SLOT, None),
95
159
  ]
96
160
 
97
161
 
98
- class ActionAskInterruptedFlowToContinue(Action):
162
+ class ActionCancelInterruptedFlows(Action):
99
163
  def name(self) -> str:
100
- return ACTION_ASK_INTERRUPTED_FLOW_TO_CONTINUE
164
+ return ACTION_CANCEL_INTERRUPTED_FLOWS
101
165
 
102
166
  async def run(
103
167
  self,
@@ -107,107 +171,114 @@ class ActionAskInterruptedFlowToContinue(Action):
107
171
  domain: Domain,
108
172
  metadata: Optional[Dict[Text, Any]] = None,
109
173
  ) -> List[Event]:
110
- interrupted_flows = tracker.get_slot(INTERRUPTED_FLOWS_SLOT) or []
111
-
112
- buttons = [
113
- {
114
- "title": flow_id,
115
- "payload": f'/SetSlots{{"{INTERRUPTED_FLOW_TO_CONTINUE_SLOT}": '
116
- f'"{flow_id}"}}',
117
- }
118
- for flow_id in interrupted_flows or []
119
- ]
120
- buttons.append(
121
- {
122
- "title": "None of them",
123
- "payload": f'/SetSlots{{"{INTERRUPTED_FLOW_TO_CONTINUE_SLOT}": '
124
- f'"none"}}',
125
- }
126
- )
174
+ # get the pattern frame from the stack
175
+ pattern_frame = get_active_continue_interrupted_pattern_frame(tracker.stack)
127
176
 
128
- await output_channel.send_text_with_buttons(
129
- tracker.sender_id,
130
- "You previously started several other tasks. "
131
- "Would you like to resume any of them?",
132
- buttons=buttons,
133
- )
177
+ if pattern_frame is None:
178
+ structlogger.warning(
179
+ "action.continue_interrupted_flows.no_continue_interrupted_frame"
180
+ )
181
+ return []
134
182
 
135
- return []
183
+ interrupted_flow_ids = pattern_frame.interrupted_flow_ids
136
184
 
185
+ event_list: List[Event] = []
137
186
 
138
- class ActionContinueInterruptedFlow(Action):
139
- def name(self) -> str:
140
- return ACTION_CONTINUE_INTERRUPTED_FLOW
187
+ # cancel all interrupted flows
188
+ for flow_id in interrupted_flow_ids:
189
+ event_list.extend(self.cancel_flow(tracker, tracker.stack, flow_id))
141
190
 
142
- async def run(
191
+ return event_list + [
192
+ SlotSet(INTERRUPTED_FLOW_TO_CONTINUE_SLOT, None),
193
+ SlotSet(CONTINUE_INTERRUPTED_FLOW_CONFIRMATION_SLOT, None),
194
+ ]
195
+
196
+ def cancel_flow(
143
197
  self,
144
- output_channel: OutputChannel,
145
- nlg: NaturalLanguageGenerator,
146
198
  tracker: DialogueStateTracker,
147
- domain: Domain,
148
- metadata: Optional[Dict[Text, Any]] = None,
199
+ stack: DialogueStack,
200
+ flow_id: str,
149
201
  ) -> List[Event]:
150
- from rasa.dialogue_understanding.commands import StartFlowCommand
151
-
152
- # get all necessary slot values
153
- multiple = tracker.get_slot(MULTIPLE_FLOWS_INTERRUPTED_SLOT)
154
- selected_flow = tracker.get_slot(INTERRUPTED_FLOW_TO_CONTINUE_SLOT)
155
- interrupted_flows = tracker.get_slot(INTERRUPTED_FLOWS_SLOT) or []
202
+ """Cancels a flow by flow id."""
203
+ applied_events: List[Event] = []
156
204
 
157
- original_user_frame = top_user_flow_frame(tracker.stack)
205
+ frames_to_cancel, user_frame_to_cancel = self._collect_frames_to_cancel(
206
+ stack, flow_id
207
+ )
158
208
 
159
- # case of multiple interrupted flows, where the user selected a flow to continue
160
- if multiple:
161
- flow_id = selected_flow if selected_flow else None
162
- # case of single interrupted flow, so there is only one flow to continue
163
- else:
164
- flow_id = interrupted_flows[0] if interrupted_flows else None
165
-
166
- event_list = []
167
- if flow_id:
168
- # TODO: refactor to avoid creating a StartFlowCommand first
169
- # resume the flow with the provided flow id
170
- start_flow_command = StartFlowCommand(flow_id)
171
- event_list = start_flow_command.resume_flow(
172
- tracker, tracker.stack, original_user_frame
173
- )
174
- else:
175
- await output_channel.send_text_message(
176
- tracker.sender_id,
177
- "You haven't selected a valid task to resume. "
178
- "Please specify the task you would like to continue.",
209
+ # if the flow is not on the stack, do nothing
210
+ if user_frame_to_cancel is None:
211
+ structlogger.error(
212
+ "cancel_flow.no_user_frame_to_cancel",
213
+ flow_id=flow_id,
179
214
  )
215
+ return []
180
216
 
181
- return event_list
217
+ frames_ids_to_cancel = [frame.frame_id for frame in frames_to_cancel]
182
218
 
219
+ stack.push(
220
+ CancelPatternFlowStackFrame(
221
+ canceled_name=flow_id,
222
+ canceled_frames=frames_ids_to_cancel,
223
+ )
224
+ )
183
225
 
184
- class ActionCancelInterruptedFlow(Action):
185
- def name(self) -> str:
186
- return ACTION_CANCEL_INTERRUPTED_FLOW
226
+ # create flow cancelled event
227
+ applied_events.extend(
228
+ [
229
+ FlowCancelled(
230
+ user_frame_to_cancel.flow_id, user_frame_to_cancel.step_id
231
+ ),
232
+ ]
233
+ )
234
+ # create agent cancelled events for any agent frames that are on the stack
235
+ for frame in frames_to_cancel:
236
+ if isinstance(frame, AgentStackFrame):
237
+ applied_events.append(
238
+ AgentCancelled(
239
+ frame.agent_id, frame.flow_id, reason="Flow was cancelled"
240
+ )
241
+ )
187
242
 
188
- async def run(
189
- self,
190
- output_channel: OutputChannel,
191
- nlg: NaturalLanguageGenerator,
192
- tracker: DialogueStateTracker,
193
- domain: Domain,
194
- metadata: Optional[Dict[Text, Any]] = None,
195
- ) -> List[Event]:
196
- from rasa.dialogue_understanding.commands import CancelFlowCommand
243
+ update_stack_events = tracker.create_stack_updated_events(stack)
197
244
 
198
- interrupted_flows = tracker.get_slot(INTERRUPTED_FLOWS_SLOT) or []
245
+ return applied_events + update_stack_events
199
246
 
200
- event_list = []
201
- for flow_id in interrupted_flows:
202
- # TODO: refactor to avoid creating a CancelFlowCommand first
203
- cancel_flow_command = CancelFlowCommand()
204
- event_list.extend(
205
- cancel_flow_command.cancel_flow(tracker, tracker.stack, flow_id)
206
- )
247
+ def _collect_frames_to_cancel(
248
+ self, stack: DialogueStack, target_flow_id: str
249
+ ) -> Tuple[List[DialogueStackFrame], Optional[UserFlowStackFrame]]:
250
+ """Collect frames that need to be cancelled.
207
251
 
208
- return event_list + [
209
- SlotSet(INTERRUPTED_FLOWS_SLOT, None),
210
- SlotSet(INTERRUPTED_FLOW_TO_CONTINUE_SLOT, None),
211
- SlotSet(MULTIPLE_FLOWS_INTERRUPTED_SLOT, None),
212
- SlotSet(CONFIRMATION_CONTINUE_INTERRUPTED_FLOW_SLOT, None),
213
- ]
252
+ Args:
253
+ stack: The stack to collect frames from.
254
+ target_flow_id: The ID of the flow to cancel.
255
+
256
+ Returns:
257
+ A tuple containing (frames_to_cancel, frame_to_cancel).
258
+ """
259
+ frames_to_cancel: List[DialogueStackFrame] = []
260
+ frame_found = False
261
+ frame_to_cancel = None
262
+
263
+ # collect all frames that belong to the target flow
264
+ # i.e. we want to cancel all frames that are on the stack and between
265
+ # the user flow frame that belongs to the target flow and the next user
266
+ # flow frame that belongs to a different flow
267
+ # this includes any pattern frames or agent frames as well
268
+ for frame in stack.frames:
269
+ if isinstance(frame, UserFlowStackFrame) and (
270
+ frame.frame_type == FlowStackFrameType.REGULAR
271
+ or frame.frame_type == FlowStackFrameType.INTERRUPT
272
+ ):
273
+ if frame.flow_id == target_flow_id:
274
+ frames_to_cancel.append(frame)
275
+ frame_to_cancel = frame
276
+ frame_found = True
277
+ continue
278
+ elif frame_found:
279
+ break
280
+
281
+ if frame_found:
282
+ frames_to_cancel.append(frame)
283
+
284
+ return list(frames_to_cancel), frame_to_cancel
@@ -1,16 +1,17 @@
1
1
  version: "3.1"
2
2
  responses:
3
3
 
4
- utter_ask_continue_interrupted_flow:
5
- - text: "Would you like to continue with {{ context.previous_flow_name }}?"
4
+ utter_ask_continue_interrupted_flow_confirmation:
5
+ - text: "Would you like to continue with {{context.interrupted_flow_options}}?"
6
+ metadata:
7
+ rephrase: True
8
+ template: jinja
9
+
10
+ utter_ask_interrupted_flow_to_continue:
11
+ - text: "Would you like to resume {{context.interrupted_flow_options}}?"
6
12
  metadata:
7
13
  rephrase: True
8
14
  template: jinja
9
- buttons:
10
- - title: "Yes"
11
- payload: "/SetSlots(confirmation_continue_interrupted_flow=true)"
12
- - title: "No"
13
- payload: "/SetSlots(confirmation_continue_interrupted_flow=false)"
14
15
 
15
16
  utter_ask_rephrase:
16
17
  - text: I’m sorry I am unable to understand you, could you please rephrase?
@@ -129,19 +130,14 @@ slots:
129
130
  type: float
130
131
  initial_value: 0.0
131
132
  max_value: 1000000
132
- interrupted_flows:
133
- type: list
134
- multiple_flows_interrupted:
135
- type: bool
136
- initial_value: false
137
- confirmation_continue_interrupted_flow:
138
- type: bool
139
- mappings:
140
- - type: from_llm
141
133
  interrupted_flow_to_continue:
142
134
  type: text
143
135
  mappings:
144
136
  - type: from_llm
137
+ continue_interrupted_flow_confirmation:
138
+ type: bool
139
+ mappings:
140
+ - type: from_llm
145
141
 
146
142
  flows:
147
143
  pattern_cancel_flow:
@@ -228,10 +224,9 @@ flows:
228
224
  description: Conversation repair flow for managing when users switch between different flows
229
225
  name: pattern continue interrupted
230
226
  steps:
231
- - action: action_set_interrupted_flows
232
227
  - noop: true
233
228
  next:
234
- - if: slots.multiple_flows_interrupted
229
+ - if: context.multiple_flows_interrupted
235
230
  then:
236
231
  - collect: interrupted_flow_to_continue
237
232
  description: "Fill this slot with the name of the flow the user wants to continue. If the user does not want to continue any of the interrupted flows, fill this slot with 'none'."
@@ -241,19 +236,18 @@ flows:
241
236
  - action: action_continue_interrupted_flow
242
237
  next: END
243
238
  - else:
244
- - action: action_cancel_interrupted_flow
239
+ - action: action_cancel_interrupted_flows
245
240
  next: END
246
241
  - else:
247
- - collect: confirmation_continue_interrupted_flow
242
+ - collect: continue_interrupted_flow_confirmation
248
243
  description: "If the user wants to continue the interrupted flow, fill this slot with true. If the user does not want to continue the interrupted flow, fill this slot with false."
249
- utter: utter_ask_continue_interrupted_flow
250
244
  next:
251
- - if: slots.confirmation_continue_interrupted_flow
245
+ - if: slots.continue_interrupted_flow_confirmation
252
246
  then:
253
247
  - action: action_continue_interrupted_flow
254
248
  next: END
255
249
  - else:
256
- - action: action_cancel_interrupted_flow
250
+ - action: action_cancel_interrupted_flows
257
251
  next: END
258
252
 
259
253
  pattern_correction:
@@ -17,6 +17,9 @@ from rasa.shared.core.flows.steps.constants import END_STEP
17
17
  from rasa.shared.core.flows.steps.continuation import ContinueFlowStep
18
18
 
19
19
  if typing.TYPE_CHECKING:
20
+ from rasa.dialogue_understanding.patterns.continue_interrupted import (
21
+ ContinueInterruptedPatternFlowStackFrame,
22
+ )
20
23
  from rasa.shared.core.trackers import DialogueStateTracker
21
24
 
22
25
 
@@ -171,10 +174,40 @@ def user_flows_on_the_stack(dialogue_stack: DialogueStack) -> Set[str]:
171
174
  All user flows that are currently on the stack.
172
175
  """
173
176
  return {
174
- f.flow_id for f in dialogue_stack.frames if isinstance(f, UserFlowStackFrame)
177
+ frame.flow_id
178
+ for frame in user_frames_on_the_stack(
179
+ dialogue_stack, ignore_call_and_link_frames=False
180
+ )
175
181
  }
176
182
 
177
183
 
184
+ def user_frames_on_the_stack(
185
+ dialogue_stack: DialogueStack, ignore_call_and_link_frames: bool = True
186
+ ) -> List[UserFlowStackFrame]:
187
+ """Get all user frames that are currently on the stack.
188
+
189
+ Args:
190
+ dialogue_stack: The dialogue stack.
191
+ ignore_call_and_link_frames: Whether to ignore user frames of type `call`
192
+ and `link`. By default, these frames are ignored.
193
+
194
+ Returns:
195
+ All user frames that are currently on the stack.
196
+ """
197
+ return [
198
+ frame
199
+ for frame in dialogue_stack.frames
200
+ if isinstance(frame, UserFlowStackFrame)
201
+ and (
202
+ not ignore_call_and_link_frames
203
+ or (
204
+ frame.frame_type != FlowStackFrameType.CALL
205
+ and frame.frame_type != FlowStackFrameType.LINK
206
+ )
207
+ )
208
+ ]
209
+
210
+
178
211
  def end_top_user_flow(stack: DialogueStack) -> DialogueStack:
179
212
  """Ends all frames on top of the stack including the topmost user frame.
180
213
 
@@ -235,13 +268,19 @@ def get_collect_steps_excluding_ask_before_filling_for_active_flow(
235
268
 
236
269
  def is_continue_interrupted_flow_active(stack: DialogueStack) -> bool:
237
270
  """Check if the continue interrupted flow is active."""
271
+ return get_active_continue_interrupted_pattern_frame(stack) is not None
272
+
273
+
274
+ def get_active_continue_interrupted_pattern_frame(
275
+ stack: DialogueStack,
276
+ ) -> Optional["ContinueInterruptedPatternFlowStackFrame"]:
238
277
  from rasa.dialogue_understanding.patterns.continue_interrupted import (
239
278
  ContinueInterruptedPatternFlowStackFrame,
240
279
  )
241
280
 
242
281
  for frame in reversed(stack.frames):
243
282
  if isinstance(frame, ContinueInterruptedPatternFlowStackFrame):
244
- return True
283
+ return frame
245
284
  if isinstance(frame, UserFlowStackFrame):
246
- return False
247
- return False
285
+ return None
286
+ return None
@@ -1,9 +1,9 @@
1
+ import typing
1
2
  from contextlib import contextmanager
2
3
  from typing import Any, Dict, Generator, List, Optional, Text
3
4
 
4
5
  import structlog
5
6
 
6
- from rasa.dialogue_understanding.commands import Command, NoopCommand, SetSlotCommand
7
7
  from rasa.dialogue_understanding.constants import (
8
8
  RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME,
9
9
  )
@@ -23,6 +23,9 @@ from rasa.shared.nlu.training_data.message import Message
23
23
  from rasa.shared.providers.llm.llm_response import LLMResponse
24
24
  from rasa.utils.common import get_bool_env_variable
25
25
 
26
+ if typing.TYPE_CHECKING:
27
+ from rasa.dialogue_understanding.commands import Command
28
+
26
29
  record_commands_and_prompts = get_bool_env_variable(
27
30
  RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME, False
28
31
  )
@@ -41,7 +44,7 @@ def set_record_commands_and_prompts() -> Generator:
41
44
 
42
45
 
43
46
  def add_commands_to_message_parse_data(
44
- message: Message, component_name: str, commands: List[Command]
47
+ message: Message, component_name: str, commands: List["Command"]
45
48
  ) -> None:
46
49
  """Add commands to the message parse data.
47
50
 
@@ -144,6 +147,11 @@ def _handle_via_nlu_in_coexistence(
144
147
  tracker: Optional[DialogueStateTracker], message: Message
145
148
  ) -> bool:
146
149
  """Check if the message should be handled by the NLU subsystem in coexistence mode.""" # noqa: E501
150
+ from rasa.dialogue_understanding.commands import (
151
+ NoopCommand,
152
+ SetSlotCommand,
153
+ )
154
+
147
155
  if not tracker:
148
156
  return False
149
157
 
@@ -156,8 +164,7 @@ def _handle_via_nlu_in_coexistence(
156
164
  "utils.handle_via_nlu_in_coexistence"
157
165
  ".tracker_missing_route_session_to_calm_slot",
158
166
  event_info=(
159
- f"Tracker doesn't have the '{ROUTE_TO_CALM_SLOT}' slot."
160
- f"Routing to CALM."
167
+ f"Tracker doesn't have the '{ROUTE_TO_CALM_SLOT}' slot.Routing to CALM."
161
168
  ),
162
169
  route_session_to_calm=commands,
163
170
  )
@@ -218,3 +225,16 @@ def _handle_via_nlu_in_coexistence(
218
225
  commands=commands,
219
226
  )
220
227
  return False
228
+
229
+
230
+ def assemble_options_string(names: List[str], conjunction: str = "and") -> str:
231
+ """Concatenate options to a human-readable string."""
232
+ option_message = ""
233
+ for i, name in enumerate(names):
234
+ if i == 0:
235
+ option_message += name
236
+ elif i == len(names) - 1:
237
+ option_message += f" {conjunction} {name}"
238
+ else:
239
+ option_message += f", {name}"
240
+ return option_message
rasa/model_training.py CHANGED
@@ -145,22 +145,23 @@ def _check_unresolved_slots(domain: Domain, stories: StoryGraph) -> None:
145
145
 
146
146
 
147
147
  async def train(
148
- domain: Text,
149
- config: Text,
150
- training_files: Optional[Union[Text, List[Text]]],
151
- output: Text = rasa.shared.constants.DEFAULT_MODELS_PATH,
148
+ domain: str,
149
+ config: str,
150
+ training_files: Optional[Union[str, List[str]]],
151
+ output: str = rasa.shared.constants.DEFAULT_MODELS_PATH,
152
152
  dry_run: bool = False,
153
153
  force_training: bool = False,
154
- fixed_model_name: Optional[Text] = None,
154
+ fixed_model_name: Optional[str] = None,
155
155
  persist_nlu_training_data: bool = False,
156
156
  core_additional_arguments: Optional[Dict] = None,
157
157
  nlu_additional_arguments: Optional[Dict] = None,
158
- model_to_finetune: Optional[Text] = None,
158
+ model_to_finetune: Optional[str] = None,
159
159
  finetuning_epoch_fraction: float = 1.0,
160
160
  remote_storage: Optional[StorageType] = None,
161
161
  file_importer: Optional[TrainingDataImporter] = None,
162
162
  keep_local_model_copy: bool = False,
163
163
  remote_root_only: bool = False,
164
+ sub_agents: Optional[str] = None,
164
165
  ) -> TrainingResult:
165
166
  """Trains a Rasa model (Core and NLU).
166
167
 
@@ -190,6 +191,7 @@ async def train(
190
191
  remote storage is configured.
191
192
  remote_root_only: If `True`, the model will be stored in the root of the
192
193
  remote model storage.
194
+ sub_agents: Path to sub-agents directory.
193
195
 
194
196
  Returns:
195
197
  An instance of `TrainingResult`.
rasa/shared/constants.py CHANGED
@@ -249,6 +249,9 @@ _VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY = "missing_keys"
249
249
  LLM_API_HEALTH_CHECK_ENV_VAR = "LLM_API_HEALTH_CHECK"
250
250
  LLM_API_HEALTH_CHECK_DEFAULT_VALUE = "false"
251
251
 
252
+ MCP_API_HEALTH_CHECK_ENV_VAR = "MCP_API_HEALTH_CHECK"
253
+ MCP_API_HEALTH_CHECK_DEFAULT_VALUE = "false"
254
+
252
255
  AWS_REGION_NAME_CONFIG_KEY = "aws_region_name"
253
256
  AWS_ACCESS_KEY_ID_CONFIG_KEY = "aws_access_key_id"
254
257
  AWS_SECRET_ACCESS_KEY_CONFIG_KEY = "aws_secret_access_key"