rasa-pro 3.13.6__py3-none-any.whl → 3.14.0.dev1__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 (178) hide show
  1. rasa/agents/__init__.py +0 -0
  2. rasa/agents/agent_factory.py +122 -0
  3. rasa/agents/agent_manager.py +162 -0
  4. rasa/agents/constants.py +31 -0
  5. rasa/agents/core/__init__.py +0 -0
  6. rasa/agents/core/agent_protocol.py +108 -0
  7. rasa/agents/core/types.py +70 -0
  8. rasa/agents/exceptions.py +8 -0
  9. rasa/agents/protocol/__init__.py +5 -0
  10. rasa/agents/protocol/a2a/__init__.py +0 -0
  11. rasa/agents/protocol/a2a/a2a_agent.py +51 -0
  12. rasa/agents/protocol/mcp/__init__.py +0 -0
  13. rasa/agents/protocol/mcp/mcp_base_agent.py +697 -0
  14. rasa/agents/protocol/mcp/mcp_open_agent.py +275 -0
  15. rasa/agents/protocol/mcp/mcp_task_agent.py +447 -0
  16. rasa/agents/schemas/__init__.py +6 -0
  17. rasa/agents/schemas/agent_input.py +24 -0
  18. rasa/agents/schemas/agent_output.py +26 -0
  19. rasa/agents/schemas/agent_tool_result.py +51 -0
  20. rasa/agents/schemas/agent_tool_schema.py +112 -0
  21. rasa/agents/templates/__init__.py +0 -0
  22. rasa/agents/templates/mcp_open_agent_prompt_template.jinja2 +15 -0
  23. rasa/agents/templates/mcp_task_agent_prompt_template.jinja2 +13 -0
  24. rasa/agents/utils.py +72 -0
  25. rasa/api.py +5 -0
  26. rasa/cli/arguments/default_arguments.py +12 -0
  27. rasa/cli/arguments/run.py +2 -0
  28. rasa/cli/dialogue_understanding_test.py +4 -0
  29. rasa/cli/e2e_test.py +4 -0
  30. rasa/cli/inspect.py +3 -0
  31. rasa/cli/llm_fine_tuning.py +5 -0
  32. rasa/cli/run.py +4 -0
  33. rasa/cli/shell.py +3 -0
  34. rasa/cli/train.py +2 -2
  35. rasa/constants.py +6 -0
  36. rasa/core/actions/action.py +69 -39
  37. rasa/core/actions/action_run_slot_rejections.py +1 -1
  38. rasa/core/agent.py +16 -0
  39. rasa/core/available_agents.py +196 -0
  40. rasa/core/available_endpoints.py +30 -0
  41. rasa/core/channels/development_inspector.py +47 -14
  42. rasa/core/channels/inspector/dist/assets/{arc-0b11fe30.js → arc-2e78c586.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-9eef30a7.js → blockDiagram-38ab4fdb-806b712e.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-03e94f28.js → c4Diagram-3d4e48cf-0745efa9.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/channel-c436ca7c.js +1 -0
  46. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-95c09eba.js → classDiagram-70f12bd4-7bd1082b.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-38e8446c.js → classDiagram-v2-f2320105-d937ba49.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/clone-50dd656b.js +1 -0
  49. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-57dc3038.js → createText-2e5e7dd3-a2a564ca.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-4bac0545.js → edges-e0da2a9e-b5256940.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-81795c90.js → erDiagram-9861fffd-e6883ad2.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-89489ae6.js → flowDb-956e92f1-e576fc02.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-cd152627.js → flowDiagram-66a62f08-2e298d01.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2b2aeaf8.js +1 -0
  55. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-3da369bc.js → flowchart-elk-definition-4a651766-dd7b150a.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-85ec16f8.js → ganttDiagram-c361ad54-5b79575c.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-495bc140.js → gitGraphDiagram-72cf32ee-3016f40a.js} +1 -1
  58. rasa/core/channels/inspector/dist/assets/{graph-1ec4d266.js → graph-3e19170f.js} +1 -1
  59. rasa/core/channels/inspector/dist/assets/index-1bd9135e.js +1353 -0
  60. rasa/core/channels/inspector/dist/assets/{index-3862675e-0a0e97c9.js → index-3862675e-eb9c86de.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-4d54bcde.js → infoDiagram-f8f76790-b4280e4d.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-dc097114.js → journeyDiagram-49397b02-556091f8.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{layout-1a08981e.js → layout-08436411.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{line-95f7f1d3.js → line-683c4f3b.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{linear-97e69543.js → linear-cee6d791.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-8c71ff03.js → mindmap-definition-fc14e90a-a0bf0b1a.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-f14c71c7.js → pieDiagram-8a3498a8-3730d5c4.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-f1d3c9ff.js → quadrantDiagram-120e2f19-12a20fed.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-bfa2412f.js → requirementDiagram-deff3bca-b9732102.js} +1 -1
  70. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-53f2c97b.js → sankeyDiagram-04a897e0-a2e72776.js} +1 -1
  71. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-319d7c0e.js → sequenceDiagram-704730f1-8b7a76bb.js} +1 -1
  72. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-76a09418.js → stateDiagram-587899a1-e65853ac.js} +1 -1
  73. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-a67f15d4.js → stateDiagram-v2-d93cdb3a-6f58a44b.js} +1 -1
  74. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-0654e7c3.js → styles-6aaf32cf-df25b934.js} +1 -1
  75. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1394bb9d.js → styles-9a916d00-88357141.js} +1 -1
  76. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-e4c5bdae.js → styles-c10674c1-d600174d.js} +1 -1
  77. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-50957104.js → svgDrawCommon-08f97a94-4adc3e0b.js} +1 -1
  78. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-b0885a6a.js → timeline-definition-85554ec2-42816fa1.js} +1 -1
  79. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-79e6541a.js → xychartDiagram-e933f94c-621eb66a.js} +1 -1
  80. rasa/core/channels/inspector/dist/index.html +2 -2
  81. rasa/core/channels/inspector/index.html +1 -1
  82. rasa/core/channels/inspector/src/App.tsx +53 -7
  83. rasa/core/channels/inspector/src/components/Chat.tsx +3 -2
  84. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +1 -1
  85. rasa/core/channels/inspector/src/components/DialogueStack.tsx +7 -5
  86. rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +268 -0
  87. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -2
  88. rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +8 -3
  89. rasa/core/channels/inspector/src/helpers/formatters.ts +24 -3
  90. rasa/core/channels/inspector/src/theme/base/styles.ts +19 -1
  91. rasa/core/channels/inspector/src/types.ts +12 -0
  92. rasa/core/channels/studio_chat.py +125 -34
  93. rasa/core/channels/voice_ready/twilio_voice.py +1 -1
  94. rasa/core/channels/voice_stream/audiocodes.py +9 -6
  95. rasa/core/channels/voice_stream/browser_audio.py +39 -4
  96. rasa/core/channels/voice_stream/call_state.py +13 -2
  97. rasa/core/channels/voice_stream/genesys.py +16 -13
  98. rasa/core/channels/voice_stream/jambonz.py +13 -11
  99. rasa/core/channels/voice_stream/twilio_media_streams.py +14 -13
  100. rasa/core/channels/voice_stream/util.py +11 -1
  101. rasa/core/channels/voice_stream/voice_channel.py +101 -29
  102. rasa/core/constants.py +4 -0
  103. rasa/core/nlg/contextual_response_rephraser.py +11 -7
  104. rasa/core/nlg/generator.py +21 -5
  105. rasa/core/nlg/response.py +43 -6
  106. rasa/core/nlg/translate.py +8 -0
  107. rasa/core/policies/enterprise_search_policy.py +4 -2
  108. rasa/core/policies/flow_policy.py +2 -2
  109. rasa/core/policies/flows/flow_executor.py +374 -35
  110. rasa/core/policies/flows/mcp_tool_executor.py +240 -0
  111. rasa/core/processor.py +6 -1
  112. rasa/core/run.py +8 -1
  113. rasa/core/utils.py +21 -1
  114. rasa/dialogue_understanding/commands/__init__.py +8 -0
  115. rasa/dialogue_understanding/commands/cancel_flow_command.py +97 -4
  116. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +11 -0
  117. rasa/dialogue_understanding/commands/continue_agent_command.py +91 -0
  118. rasa/dialogue_understanding/commands/knowledge_answer_command.py +11 -0
  119. rasa/dialogue_understanding/commands/restart_agent_command.py +146 -0
  120. rasa/dialogue_understanding/commands/start_flow_command.py +129 -8
  121. rasa/dialogue_understanding/commands/utils.py +6 -2
  122. rasa/dialogue_understanding/generator/command_parser.py +4 -0
  123. rasa/dialogue_understanding/generator/llm_based_command_generator.py +50 -12
  124. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +61 -0
  125. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +61 -0
  126. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +81 -0
  127. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +81 -0
  128. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +7 -6
  129. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +7 -6
  130. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +41 -2
  131. rasa/dialogue_understanding/patterns/continue_interrupted.py +163 -1
  132. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +51 -7
  133. rasa/dialogue_understanding/processor/command_processor.py +27 -11
  134. rasa/dialogue_understanding/stack/dialogue_stack.py +123 -2
  135. rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +57 -0
  136. rasa/dialogue_understanding/stack/utils.py +3 -2
  137. rasa/dialogue_understanding_test/du_test_runner.py +7 -2
  138. rasa/dialogue_understanding_test/du_test_schema.yml +3 -3
  139. rasa/e2e_test/e2e_test_runner.py +5 -0
  140. rasa/e2e_test/e2e_test_schema.yml +3 -3
  141. rasa/model_manager/model_api.py +1 -1
  142. rasa/model_manager/socket_bridge.py +8 -2
  143. rasa/server.py +10 -0
  144. rasa/shared/agents/__init__.py +0 -0
  145. rasa/shared/agents/utils.py +35 -0
  146. rasa/shared/constants.py +5 -0
  147. rasa/shared/core/constants.py +12 -1
  148. rasa/shared/core/domain.py +5 -5
  149. rasa/shared/core/events.py +319 -0
  150. rasa/shared/core/flows/flows_list.py +2 -2
  151. rasa/shared/core/flows/flows_yaml_schema.json +101 -186
  152. rasa/shared/core/flows/steps/call.py +51 -5
  153. rasa/shared/core/flows/validation.py +45 -7
  154. rasa/shared/core/flows/yaml_flows_io.py +3 -3
  155. rasa/shared/providers/llm/_base_litellm_client.py +39 -7
  156. rasa/shared/providers/llm/litellm_router_llm_client.py +8 -4
  157. rasa/shared/providers/llm/llm_client.py +7 -3
  158. rasa/shared/providers/llm/llm_response.py +49 -0
  159. rasa/shared/providers/llm/self_hosted_llm_client.py +8 -4
  160. rasa/shared/utils/common.py +2 -1
  161. rasa/shared/utils/llm.py +28 -5
  162. rasa/shared/utils/mcp/__init__.py +0 -0
  163. rasa/shared/utils/mcp/server_connection.py +157 -0
  164. rasa/shared/utils/schemas/events.py +42 -0
  165. rasa/tracing/instrumentation/instrumentation.py +4 -2
  166. rasa/utils/common.py +53 -0
  167. rasa/utils/licensing.py +21 -10
  168. rasa/utils/plotting.py +1 -1
  169. rasa/version.py +1 -1
  170. {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/METADATA +16 -15
  171. {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/RECORD +174 -137
  172. rasa/core/channels/inspector/dist/assets/channel-51d02e9e.js +0 -1
  173. rasa/core/channels/inspector/dist/assets/clone-cc738fa6.js +0 -1
  174. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-0c716443.js +0 -1
  175. rasa/core/channels/inspector/dist/assets/index-c804b295.js +0 -1335
  176. {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/NOTICE +0 -0
  177. {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/WHEEL +0 -0
  178. {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,240 @@
1
+ import json
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ import structlog
5
+ from mcp.types import CallToolResult
6
+
7
+ from rasa.core.available_endpoints import AvailableEndpoints
8
+ from rasa.core.policies.flows.flow_step_result import (
9
+ ContinueFlowWithNextStep,
10
+ FlowStepResult,
11
+ )
12
+ from rasa.dialogue_understanding.patterns.internal_error import (
13
+ InternalErrorPatternFlowStackFrame,
14
+ )
15
+ from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
16
+ from rasa.shared.core.events import Event, SlotSet
17
+ from rasa.shared.core.flows.steps import CallFlowStep
18
+ from rasa.shared.core.trackers import DialogueStateTracker
19
+ from rasa.shared.utils.mcp.server_connection import MCPServerConnection
20
+
21
+ structlogger = structlog.get_logger()
22
+
23
+ SEPARATOR = "\n###\n"
24
+
25
+
26
+ async def call_mcp_tool(
27
+ initial_events: List[Event],
28
+ stack: DialogueStack,
29
+ step: CallFlowStep,
30
+ tracker: DialogueStateTracker,
31
+ ) -> FlowStepResult:
32
+ """Run an MCP tool call step."""
33
+ structlogger.debug(
34
+ "flow.step.call_mcp_tool",
35
+ tool_id=step.call,
36
+ mcp_server=step.mcp_server,
37
+ mapping=step.mapping,
38
+ step_id=step.id,
39
+ flow_id=step.flow_id,
40
+ )
41
+
42
+ try:
43
+ return await _execute_mcp_tool_call(initial_events, stack, step, tracker)
44
+ except Exception as e:
45
+ return _handle_mcp_tool_error(
46
+ stack,
47
+ initial_events,
48
+ error_message=f"Failed to execute MCP tool call: {e}.",
49
+ tool_name=step.call,
50
+ mcp_server=step.mcp_server,
51
+ )
52
+
53
+
54
+ async def _execute_mcp_tool_call(
55
+ initial_events: List[Event],
56
+ stack: DialogueStack,
57
+ step: CallFlowStep,
58
+ tracker: DialogueStateTracker,
59
+ ) -> FlowStepResult:
60
+ """Execute the MCP tool call with proper error handling."""
61
+ mcp_server_connection = None
62
+ try:
63
+ # Connect to the MCP server
64
+ mcp_server_connection = await _connect_to_mcp_server(step.mcp_server)
65
+
66
+ if not mcp_server_connection:
67
+ return _handle_mcp_tool_error(
68
+ stack,
69
+ initial_events,
70
+ f"Cannot connect to MCP server '{step.mcp_server}'.",
71
+ tool_name=step.call,
72
+ mcp_server=step.mcp_server,
73
+ )
74
+
75
+ # Validate tool availability
76
+ if not await _is_tool_available(mcp_server_connection, step.call):
77
+ return _handle_mcp_tool_error(
78
+ stack,
79
+ initial_events,
80
+ f"Tool '{step.call}' is not available on MCP server "
81
+ f"'{step.mcp_server}'.",
82
+ tool_name=step.call,
83
+ mcp_server=step.mcp_server,
84
+ )
85
+
86
+ # This should not happen, but we need to check for type checking to pass
87
+ if not step.mapping:
88
+ return _handle_mcp_tool_error(
89
+ stack,
90
+ initial_events,
91
+ f"No mapping found for tool '{step.call}'.",
92
+ tool_name=step.call,
93
+ mcp_server=step.mcp_server,
94
+ )
95
+
96
+ # Prepare arguments for the tool call
97
+ arguments = _prepare_tool_arguments(step.mapping["input"], tracker)
98
+
99
+ # Call the tool with parameters
100
+ mcp_server = await mcp_server_connection.ensure_active_session()
101
+ result = await mcp_server.call_tool(step.call, arguments)
102
+
103
+ # Handle tool execution result
104
+ if result.isError:
105
+ return _handle_mcp_tool_error(
106
+ stack,
107
+ initial_events,
108
+ f"Tool '{step.call}' execution failed: {result.content}.",
109
+ tool_name=step.call,
110
+ mcp_server=step.mcp_server,
111
+ )
112
+ elif not result.content:
113
+ structlogger.warning(
114
+ "call_mcp_tool.empty_tool_result",
115
+ tool_name=step.call,
116
+ mcp_server=step.mcp_server,
117
+ )
118
+ else:
119
+ structlogger.debug(
120
+ "call_mcp_tool.tool_execution_success",
121
+ tool_name=step.call,
122
+ mcp_server=step.mcp_server,
123
+ result_content=result.content,
124
+ )
125
+
126
+ # Process successful result
127
+ set_slot_event = _process_tool_result(result, step.mapping["output"])
128
+ if set_slot_event:
129
+ initial_events.append(set_slot_event)
130
+
131
+ return ContinueFlowWithNextStep(events=initial_events)
132
+
133
+ finally:
134
+ # Always clean up the connection to prevent resource leaks
135
+ if mcp_server_connection:
136
+ try:
137
+ await mcp_server_connection.close()
138
+ except Exception as e:
139
+ structlogger.warning(
140
+ "call_mcp_tool.connection_cleanup_failed",
141
+ tool_name=step.call,
142
+ mcp_server=step.mcp_server,
143
+ error=str(e),
144
+ )
145
+
146
+
147
+ async def _is_tool_available(
148
+ mcp_server_connection: MCPServerConnection, tool_name: str
149
+ ) -> bool:
150
+ """Check if the specified tool is available on the MCP server."""
151
+ try:
152
+ # Get the active session from the connection
153
+ mcp_server = await mcp_server_connection.ensure_active_session()
154
+ available_tools = await mcp_server.list_tools()
155
+ tool_names = [tool.name for tool in available_tools.tools]
156
+ return tool_name in tool_names
157
+ except Exception as e:
158
+ structlogger.warning(
159
+ "call_mcp_tool.tool_availability_check_failed",
160
+ tool_name=tool_name,
161
+ error=str(e),
162
+ )
163
+ return False
164
+
165
+
166
+ async def _connect_to_mcp_server(
167
+ mcp_server_name: Optional[str],
168
+ ) -> Optional[MCPServerConnection]:
169
+ """Connect to the MCP server."""
170
+ if not mcp_server_name:
171
+ return None
172
+
173
+ # get the MCP server config from the available endpoints
174
+ endpoints = AvailableEndpoints.get_instance()
175
+ mcp_servers = endpoints.mcp_servers
176
+ if not mcp_servers:
177
+ return None
178
+
179
+ mcp_server_configs = [
180
+ mcp_server for mcp_server in mcp_servers if mcp_server.name == mcp_server_name
181
+ ]
182
+ if not mcp_server_configs or len(mcp_server_configs) != 1:
183
+ return None
184
+
185
+ mcp_server_config = mcp_server_configs[0]
186
+ mcp_server_connection = MCPServerConnection(
187
+ mcp_server_config.name, mcp_server_config.url, mcp_server_config.type
188
+ )
189
+
190
+ # Ensure the connection is established and return the connection object
191
+ await mcp_server_connection.ensure_active_session()
192
+ return mcp_server_connection
193
+
194
+
195
+ def _prepare_tool_arguments(
196
+ input_mapping: List[Dict[str, str]], tracker: DialogueStateTracker
197
+ ) -> Dict[str, Any]:
198
+ """Prepare arguments for the tool call from slot values."""
199
+ arguments = {}
200
+ for argument in input_mapping:
201
+ slot_value = tracker.get_slot(argument["slot"])
202
+ arguments[argument["param"]] = slot_value
203
+ return arguments
204
+
205
+
206
+ def _process_tool_result(
207
+ result: CallToolResult,
208
+ output_mapping: str,
209
+ ) -> Optional[SlotSet]:
210
+ """Create a SetSlot event for the tool result."""
211
+ try:
212
+ content_as_string = [
213
+ json.dumps(content_part.model_dump()) for content_part in result.content
214
+ ]
215
+ return SlotSet(key=output_mapping, value=SEPARATOR.join(content_as_string))
216
+ except Exception as e:
217
+ structlogger.error(
218
+ "call_mcp_tool.result_processing_failed",
219
+ error=str(e),
220
+ result=result,
221
+ )
222
+ return None
223
+
224
+
225
+ def _handle_mcp_tool_error(
226
+ stack: DialogueStack,
227
+ events: List[Event],
228
+ error_message: str,
229
+ tool_name: str,
230
+ mcp_server: Optional[str],
231
+ ) -> FlowStepResult:
232
+ """Handle MCP tool errors consistently."""
233
+ structlogger.error(
234
+ "call_mcp_tool.error",
235
+ error_message=error_message,
236
+ tool_name=tool_name,
237
+ mcp_server=mcp_server,
238
+ )
239
+ stack.push(InternalErrorPatternFlowStackFrame())
240
+ return ContinueFlowWithNextStep(events=events)
rasa/core/processor.py CHANGED
@@ -69,6 +69,7 @@ from rasa.shared.constants import (
69
69
  UTTER_PREFIX,
70
70
  )
71
71
  from rasa.shared.core.constants import (
72
+ ACTION_AGENT_REQUEST_USER_INPUT_NAME,
72
73
  ACTION_CORRECT_FLOW_SLOT,
73
74
  ACTION_EXTRACT_SLOTS,
74
75
  ACTION_LISTEN_NAME,
@@ -1194,7 +1195,11 @@ class MessageProcessor:
1194
1195
  `False` if `action_name` is `ACTION_LISTEN_NAME` or
1195
1196
  `ACTION_SESSION_START_NAME`, otherwise `True`.
1196
1197
  """
1197
- return action_name not in (ACTION_LISTEN_NAME, ACTION_SESSION_START_NAME)
1198
+ return action_name not in (
1199
+ ACTION_LISTEN_NAME,
1200
+ ACTION_SESSION_START_NAME,
1201
+ ACTION_AGENT_REQUEST_USER_INPUT_NAME,
1202
+ )
1198
1203
 
1199
1204
  async def execute_side_effects(
1200
1205
  self,
rasa/core/run.py CHANGED
@@ -30,6 +30,7 @@ from rasa import server, telemetry
30
30
  from rasa.constants import ENV_SANIC_BACKLOG
31
31
  from rasa.core import agent, channels, constants
32
32
  from rasa.core.agent import Agent
33
+ from rasa.core.available_agents import AvailableAgents
33
34
  from rasa.core.available_endpoints import AvailableEndpoints
34
35
  from rasa.core.channels import console
35
36
  from rasa.core.channels.channel import InputChannel
@@ -141,6 +142,7 @@ def configure_app(
141
142
  route: Optional[Text] = "/webhooks/",
142
143
  port: int = constants.DEFAULT_SERVER_PORT,
143
144
  endpoints: Optional[AvailableEndpoints] = None,
145
+ sub_agents: Optional[AvailableAgents] = None,
144
146
  log_file: Optional[Text] = None,
145
147
  conversation_id: Optional[Text] = uuid.uuid4().hex,
146
148
  use_syslog: bool = False,
@@ -169,6 +171,7 @@ def configure_app(
169
171
  jwt_private_key=jwt_private_key,
170
172
  jwt_method=jwt_method,
171
173
  endpoints=endpoints,
174
+ sub_agents=sub_agents,
172
175
  is_inspector_enabled=is_inspector_enabled,
173
176
  )
174
177
  )
@@ -233,6 +236,7 @@ def serve_application(
233
236
  jwt_private_key: Optional[Text] = None,
234
237
  jwt_method: Optional[Text] = None,
235
238
  endpoints: Optional[AvailableEndpoints] = None,
239
+ sub_agents: Optional[AvailableAgents] = None,
236
240
  remote_storage: Optional[StorageType] = None,
237
241
  log_file: Optional[Text] = None,
238
242
  ssl_certificate: Optional[Text] = None,
@@ -273,6 +277,7 @@ def serve_application(
273
277
  jwt_method,
274
278
  port=port,
275
279
  endpoints=endpoints,
280
+ sub_agents=sub_agents,
276
281
  log_file=log_file,
277
282
  conversation_id=conversation_id,
278
283
  use_syslog=use_syslog,
@@ -292,7 +297,7 @@ def serve_application(
292
297
  logger.info(f"Starting Rasa server on {protocol}://{interface}:{port}")
293
298
 
294
299
  app.register_listener(
295
- partial(load_agent_on_start, model_path, endpoints, remote_storage),
300
+ partial(load_agent_on_start, model_path, endpoints, remote_storage, sub_agents),
296
301
  "before_server_start",
297
302
  )
298
303
 
@@ -330,6 +335,7 @@ async def load_agent_on_start(
330
335
  model_path: Text,
331
336
  endpoints: AvailableEndpoints,
332
337
  remote_storage: Optional[StorageType],
338
+ sub_agents: Optional[AvailableAgents],
333
339
  app: Sanic,
334
340
  loop: AbstractEventLoop,
335
341
  ) -> Agent:
@@ -342,6 +348,7 @@ async def load_agent_on_start(
342
348
  model_path=model_path,
343
349
  remote_storage=remote_storage,
344
350
  endpoints=endpoints,
351
+ sub_agents=sub_agents,
345
352
  loop=loop,
346
353
  )
347
354
 
rasa/core/utils.py CHANGED
@@ -1,8 +1,9 @@
1
1
  import logging
2
2
  import os
3
+ import re
3
4
  from pathlib import Path
4
5
  from socket import SOCK_DGRAM, SOCK_STREAM
5
- from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Text, Tuple, Union
6
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Text, Tuple, Union
6
7
 
7
8
  import numpy as np
8
9
  import structlog
@@ -359,3 +360,22 @@ def should_force_slot_filling(
359
360
  return True, slot_name
360
361
 
361
362
  return False, None
363
+
364
+
365
+ def get_slot_names_from_exit_conditions(exit_conditions: List[str]) -> List[str]:
366
+ """Extract slot names from exit conditions.
367
+
368
+ Args:
369
+ exit_conditions: The exit conditions to extract slot names from.
370
+
371
+ Returns:
372
+ A list of slot names.
373
+ """
374
+ # Find all unique names matching "slots.<name>"
375
+ return list(
376
+ {
377
+ name
378
+ for condition in exit_conditions
379
+ for name in re.findall(r"\bslots\.(\w+)", condition)
380
+ }
381
+ )
@@ -8,6 +8,9 @@ from rasa.dialogue_understanding.commands.chit_chat_answer_command import (
8
8
  )
9
9
  from rasa.dialogue_understanding.commands.clarify_command import ClarifyCommand
10
10
  from rasa.dialogue_understanding.commands.command import Command
11
+ from rasa.dialogue_understanding.commands.continue_agent_command import (
12
+ ContinueAgentCommand,
13
+ )
11
14
  from rasa.dialogue_understanding.commands.correct_slots_command import (
12
15
  CorrectedSlot,
13
16
  CorrectSlotsCommand,
@@ -29,6 +32,9 @@ from rasa.dialogue_understanding.commands.noop_command import NoopCommand
29
32
  from rasa.dialogue_understanding.commands.repeat_bot_messages_command import (
30
33
  RepeatBotMessagesCommand,
31
34
  )
35
+ from rasa.dialogue_understanding.commands.restart_agent_command import (
36
+ RestartAgentCommand,
37
+ )
32
38
  from rasa.dialogue_understanding.commands.restart_command import RestartCommand
33
39
  from rasa.dialogue_understanding.commands.session_end_command import SessionEndCommand
34
40
  from rasa.dialogue_understanding.commands.session_start_command import (
@@ -62,4 +68,6 @@ __all__ = [
62
68
  "SessionEndCommand",
63
69
  "RepeatBotMessagesCommand",
64
70
  "RestartCommand",
71
+ "ContinueAgentCommand",
72
+ "RestartAgentCommand",
65
73
  ]
@@ -2,10 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  from dataclasses import dataclass
5
- from typing import Any, Dict, List
5
+ from typing import Any, Dict, List, Optional, Tuple
6
6
 
7
7
  import structlog
8
8
 
9
+ from rasa.core.policies.flows.flow_executor import (
10
+ remove_agent_stack_frame,
11
+ )
9
12
  from rasa.dialogue_understanding.commands.command import Command
10
13
  from rasa.dialogue_understanding.commands.command_syntax_manager import (
11
14
  CommandSyntaxManager,
@@ -13,12 +16,13 @@ from rasa.dialogue_understanding.commands.command_syntax_manager import (
13
16
  )
14
17
  from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
15
18
  from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
19
+ from rasa.dialogue_understanding.stack.frames import DialogueStackFrame
16
20
  from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
17
21
  FlowStackFrameType,
18
22
  UserFlowStackFrame,
19
23
  )
20
24
  from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
21
- from rasa.shared.core.events import Event, FlowCancelled
25
+ from rasa.shared.core.events import AgentCancelled, Event, FlowCancelled
22
26
  from rasa.shared.core.flows import FlowsList
23
27
  from rasa.shared.core.trackers import DialogueStateTracker
24
28
 
@@ -48,8 +52,7 @@ class CancelFlowCommand(Command):
48
52
  """Selects the frames that were canceled.
49
53
 
50
54
  Args:
51
- dialogue_stack: The dialogue stack.
52
- current_flow: The current flow.
55
+ stack: The dialogue stack.
53
56
 
54
57
  Returns:
55
58
  The frames that were canceled.
@@ -106,6 +109,19 @@ class CancelFlowCommand(Command):
106
109
  )
107
110
  return []
108
111
 
112
+ if agent_frame := original_tracker.stack.find_active_agent_stack_frame_for_flow(
113
+ current_flow.id
114
+ ):
115
+ structlogger.debug(
116
+ "cancel_command.remove_agent_stack_frame",
117
+ command=self,
118
+ frame=agent_frame,
119
+ )
120
+ remove_agent_stack_frame(stack, agent_frame.agent_id)
121
+ applied_events.append(
122
+ AgentCancelled(agent_id=agent_frame.agent_id, flow_id=current_flow.id)
123
+ )
124
+
109
125
  # we pass in the original dialogue stack (before any of the currently
110
126
  # predicted commands were applied) to make sure we don't cancel any
111
127
  # frames that were added by the currently predicted commands.
@@ -159,3 +175,80 @@ class CancelFlowCommand(Command):
159
175
  CommandSyntaxManager.get_syntax_version(),
160
176
  mapper[CommandSyntaxManager.get_default_syntax_version()],
161
177
  )
178
+
179
+ def cancel_flow(
180
+ self,
181
+ tracker: DialogueStateTracker,
182
+ stack: DialogueStack,
183
+ flow_id: str,
184
+ ) -> List[Event]:
185
+ """Cancels a flow by flow id."""
186
+ applied_events: List[Event] = []
187
+
188
+ frames_to_cancel, user_frame_to_cancel = self._collect_frames_to_cancel(
189
+ stack, flow_id
190
+ )
191
+
192
+ # if the flow is not on the stack, do nothing
193
+ if user_frame_to_cancel is None:
194
+ structlogger.error(
195
+ "cancel_flow_command.cancel_flow.no_user_frame_to_cancel",
196
+ command=self,
197
+ )
198
+ return []
199
+
200
+ frames_ids_to_cancel = [frame.frame_id for frame in frames_to_cancel]
201
+
202
+ stack.push(
203
+ CancelPatternFlowStackFrame(
204
+ canceled_name=flow_id,
205
+ canceled_frames=frames_ids_to_cancel,
206
+ )
207
+ )
208
+
209
+ # create flow cancelled event
210
+ applied_events.extend(
211
+ [
212
+ FlowCancelled(
213
+ user_frame_to_cancel.flow_id, user_frame_to_cancel.step_id
214
+ ),
215
+ ]
216
+ )
217
+
218
+ update_stack_events = tracker.create_stack_updated_events(stack)
219
+
220
+ return applied_events + update_stack_events
221
+
222
+ def _collect_frames_to_cancel(
223
+ self, stack: DialogueStack, target_flow_id: str
224
+ ) -> Tuple[List[DialogueStackFrame], Optional[UserFlowStackFrame]]:
225
+ """Collect frames that need to be cancelled.
226
+
227
+ Args:
228
+ stack: The stack to collect frames from.
229
+ target_flow_id: The ID of the flow to cancel.
230
+
231
+ Returns:
232
+ A tuple containing (frames_to_cancel, frame_to_cancel).
233
+ """
234
+ frames_to_cancel: List[DialogueStackFrame] = []
235
+ frame_found = False
236
+ frame_to_cancel = None
237
+
238
+ for frame in stack.frames:
239
+ if isinstance(frame, UserFlowStackFrame) and (
240
+ frame.frame_type == FlowStackFrameType.REGULAR
241
+ or frame.frame_type == FlowStackFrameType.INTERRUPT
242
+ ):
243
+ if frame.flow_id == target_flow_id:
244
+ frames_to_cancel.append(frame)
245
+ frame_to_cancel = frame
246
+ frame_found = True
247
+ continue
248
+ elif frame_found:
249
+ break
250
+
251
+ if frame_found:
252
+ frames_to_cancel.append(frame)
253
+
254
+ return list(frames_to_cancel), frame_to_cancel
@@ -12,6 +12,10 @@ from rasa.dialogue_understanding.commands.free_form_answer_command import (
12
12
  FreeFormAnswerCommand,
13
13
  )
14
14
  from rasa.dialogue_understanding.patterns.chitchat import ChitchatPatternFlowStackFrame
15
+ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
16
+ AgentStackFrame,
17
+ AgentState,
18
+ )
15
19
  from rasa.shared.core.events import Event
16
20
  from rasa.shared.core.flows import FlowsList
17
21
  from rasa.shared.core.trackers import DialogueStateTracker
@@ -52,6 +56,13 @@ class ChitChatAnswerCommand(FreeFormAnswerCommand):
52
56
  The events to apply to the tracker.
53
57
  """
54
58
  stack = tracker.stack
59
+
60
+ # if the top stack frame is an agent stack frame, we need to
61
+ # update the state to INTERRUPTED
62
+ if top_stack_frame := stack.top():
63
+ if isinstance(top_stack_frame, AgentStackFrame):
64
+ top_stack_frame.state = AgentState.INTERRUPTED
65
+
55
66
  stack.push(ChitchatPatternFlowStackFrame())
56
67
  return tracker.create_stack_updated_events(stack)
57
68
 
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from typing import Any, Dict, List
6
+
7
+ import structlog
8
+
9
+ from rasa.dialogue_understanding.commands.command import Command
10
+ from rasa.dialogue_understanding.commands.command_syntax_manager import (
11
+ CommandSyntaxManager,
12
+ CommandSyntaxVersion,
13
+ )
14
+ from rasa.shared.core.events import Event
15
+ from rasa.shared.core.flows import FlowsList
16
+ from rasa.shared.core.trackers import DialogueStateTracker
17
+
18
+ structlogger = structlog.get_logger()
19
+
20
+
21
+ @dataclass
22
+ class ContinueAgentCommand(Command):
23
+ """A command to continue the currently active agent's execution."""
24
+
25
+ @classmethod
26
+ def command(cls) -> str:
27
+ """Returns the command type."""
28
+ return "continue agent"
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: Dict[str, Any]) -> ContinueAgentCommand:
32
+ """Converts the dictionary to a command.
33
+
34
+ Returns:
35
+ The converted dictionary.
36
+ """
37
+ return ContinueAgentCommand()
38
+
39
+ def run_command_on_tracker(
40
+ self,
41
+ tracker: DialogueStateTracker,
42
+ all_flows: FlowsList,
43
+ original_tracker: DialogueStateTracker,
44
+ ) -> List[Event]:
45
+ """Runs the command on the tracker.
46
+
47
+ Args:
48
+ tracker: The tracker to run the command on.
49
+ all_flows: All flows in the assistant.
50
+ original_tracker: The tracker before any command was executed.
51
+
52
+ Returns:
53
+ The events to apply to the tracker.
54
+ """
55
+ # do nothing
56
+ return []
57
+
58
+ def __hash__(self) -> int:
59
+ return hash(self.command())
60
+
61
+ def __eq__(self, other: object) -> bool:
62
+ return isinstance(other, ContinueAgentCommand)
63
+
64
+ def to_dsl(self) -> str:
65
+ """Converts the command to a DSL string."""
66
+ mapper = {
67
+ CommandSyntaxVersion.v1: "ContinueAgent()",
68
+ CommandSyntaxVersion.v2: "continue agent",
69
+ CommandSyntaxVersion.v3: "continue agent",
70
+ }
71
+ return mapper.get(
72
+ CommandSyntaxManager.get_syntax_version(),
73
+ mapper[CommandSyntaxManager.get_default_syntax_version()],
74
+ )
75
+
76
+ @classmethod
77
+ def from_dsl(cls, match: re.Match, **kwargs: Any) -> ContinueAgentCommand:
78
+ """Converts a DSL string to a command."""
79
+ return ContinueAgentCommand()
80
+
81
+ @staticmethod
82
+ def regex_pattern() -> str:
83
+ mapper = {
84
+ CommandSyntaxVersion.v1: r"ContinueAgent\(\)",
85
+ CommandSyntaxVersion.v2: r"""^[\s\W\d]*continue agent['"`]*$""",
86
+ CommandSyntaxVersion.v3: r"""^[\s\W\d]*continue agent['"`]*$""",
87
+ }
88
+ return mapper.get(
89
+ CommandSyntaxManager.get_syntax_version(),
90
+ mapper[CommandSyntaxManager.get_default_syntax_version()],
91
+ )
@@ -12,6 +12,10 @@ from rasa.dialogue_understanding.commands.free_form_answer_command import (
12
12
  FreeFormAnswerCommand,
13
13
  )
14
14
  from rasa.dialogue_understanding.patterns.search import SearchPatternFlowStackFrame
15
+ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
16
+ AgentStackFrame,
17
+ AgentState,
18
+ )
15
19
  from rasa.shared.core.events import Event
16
20
  from rasa.shared.core.flows import FlowsList
17
21
  from rasa.shared.core.trackers import DialogueStateTracker
@@ -52,6 +56,13 @@ class KnowledgeAnswerCommand(FreeFormAnswerCommand):
52
56
  The events to apply to the tracker.
53
57
  """
54
58
  stack = tracker.stack
59
+
60
+ # if the top stack frame is an agent stack frame, we need to
61
+ # update the state to INTERRUPTED
62
+ if top_stack_frame := stack.top():
63
+ if isinstance(top_stack_frame, AgentStackFrame):
64
+ top_stack_frame.state = AgentState.INTERRUPTED
65
+
55
66
  stack.push(SearchPatternFlowStackFrame())
56
67
  return tracker.create_stack_updated_events(stack)
57
68