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,447 @@
1
+ import importlib
2
+ import json
3
+ import re
4
+ from typing import Any, Dict, List, Optional, Tuple
5
+
6
+ import structlog
7
+ from jinja2 import Template
8
+ from pypred import Predicate
9
+
10
+ from rasa.agents.constants import (
11
+ KEY_CONTENT,
12
+ KEY_ROLE,
13
+ KEY_TOOL_CALL_ID,
14
+ )
15
+ from rasa.agents.core.types import AgentStatus, ProtocolType
16
+ from rasa.agents.protocol.mcp.mcp_base_agent import MCPBaseAgent
17
+ from rasa.agents.schemas import (
18
+ AgentInput,
19
+ AgentOutput,
20
+ AgentToolResult,
21
+ )
22
+ from rasa.core.available_agents import AgentMCPServerConfig, ProtocolConfig
23
+ from rasa.shared.agents.utils import make_agent_identifier
24
+ from rasa.shared.constants import (
25
+ ROLE_TOOL,
26
+ )
27
+ from rasa.shared.core.events import SlotSet
28
+ from rasa.shared.providers.llm.llm_response import LLMResponse
29
+
30
+ DEFAULT_TASK_AGENT_PROMPT_TEMPLATE = importlib.resources.read_text(
31
+ "rasa.agents.templates", "mcp_task_agent_prompt_template.jinja2"
32
+ )
33
+
34
+ structlogger = structlog.get_logger()
35
+
36
+
37
+ class MCPTaskAgent(MCPBaseAgent):
38
+ """MCPTaskAgent client implementation"""
39
+
40
+ def __init__(
41
+ self,
42
+ name: str,
43
+ description: str,
44
+ protocol_type: ProtocolConfig,
45
+ server_configs: List[AgentMCPServerConfig],
46
+ llm_config: Optional[Dict[str, Any]] = None,
47
+ prompt_template: Optional[str] = None,
48
+ timeout: Optional[int] = None,
49
+ max_retries: Optional[int] = None,
50
+ ):
51
+ super().__init__(
52
+ name,
53
+ description,
54
+ protocol_type,
55
+ server_configs,
56
+ llm_config,
57
+ prompt_template,
58
+ timeout,
59
+ max_retries,
60
+ )
61
+
62
+ @property
63
+ def protocol_type(self) -> ProtocolType:
64
+ return ProtocolType.MCP_TASK
65
+
66
+ @staticmethod
67
+ def get_default_prompt_template() -> str:
68
+ return DEFAULT_TASK_AGENT_PROMPT_TEMPLATE
69
+
70
+ @classmethod
71
+ def get_agent_specific_built_in_tools(
72
+ cls, agent_input: AgentInput
73
+ ) -> List[Dict[str, Any]]:
74
+ """Get agentic specific built-in tools."""
75
+ return [
76
+ cls.get_slot_specific_set_slot_tool(slot_name)
77
+ for slot_name in cls._get_slot_names_from_exit_conditions(agent_input)
78
+ ]
79
+
80
+ @classmethod
81
+ def _get_slot_names_from_exit_conditions(cls, agent_input: AgentInput) -> List[str]:
82
+ """Extract valid slot names from exit conditions."""
83
+ exit_conditions = agent_input.metadata.get("exit_if", [])
84
+
85
+ # Find all unique names matching "slots.<name>"
86
+ extracted_slot_names = {
87
+ name
88
+ for condition in exit_conditions
89
+ for name in re.findall(r"\bslots\.(\w+)", condition)
90
+ }
91
+
92
+ # Keep only slots that actually exist in agent_input.slots
93
+ valid_slot_names = [
94
+ slot_name
95
+ for slot_name in extracted_slot_names
96
+ if slot_name in agent_input.slots
97
+ ]
98
+
99
+ structlogger.debug(
100
+ "mcp_task_agent.get_agent_specific_built_in_tools.slot_names",
101
+ exit_conditions=exit_conditions,
102
+ slot_names=valid_slot_names,
103
+ )
104
+
105
+ return valid_slot_names
106
+
107
+ @classmethod
108
+ def get_slot_specific_set_slot_tool(cls, slot_name: str) -> Dict[str, Any]:
109
+ """Get the set slot tool."""
110
+ return {
111
+ "type": "function",
112
+ "function": {
113
+ "name": f"set_slot_{slot_name}",
114
+ # We need to add the description, slot type, and allowed values
115
+ # (if present) to the description as well.
116
+ "description": f"Set the slot {slot_name} to a specific value.",
117
+ "parameters": {
118
+ "type": "object",
119
+ "properties": {
120
+ "slot_value": {
121
+ "type": "string",
122
+ "description": "The value to assign to the slot.",
123
+ },
124
+ },
125
+ "required": ["slot_value"],
126
+ "additionalProperties": False,
127
+ },
128
+ "strict": True,
129
+ },
130
+ }
131
+
132
+ @classmethod
133
+ def _is_exit_conditions_met(
134
+ cls, agent_input: AgentInput, slots: Dict[str, Any]
135
+ ) -> Tuple[bool, Optional[str]]:
136
+ """Check if the exit conditions are met.
137
+
138
+ Args:
139
+ agent_input: The agent input.
140
+ slots: The slots to check the exit conditions against.
141
+
142
+ Returns:
143
+ A tuple containing a boolean indicating if the exit conditions are met
144
+ and a string indicating if an internal error occurred.
145
+ """
146
+ if not slots:
147
+ return False, None
148
+
149
+ exit_conditions = agent_input.metadata.get("exit_if", [])
150
+ current_context = {"slots": slots}
151
+
152
+ internal_error = None
153
+ all_conditions_met = True
154
+
155
+ for condition in exit_conditions:
156
+ try:
157
+ rendered_template = Template(condition).render(current_context)
158
+ predicate = Predicate(rendered_template)
159
+ condition_result = predicate.evaluate(current_context)
160
+ structlogger.debug(
161
+ "mcp_task_agent.is_exit_conditions_met.predicate.result",
162
+ predicate=predicate.description(),
163
+ condition_result=condition_result,
164
+ slots=slots,
165
+ rendered_template=rendered_template,
166
+ )
167
+
168
+ # All conditions must be met (AND logic)
169
+ if not condition_result:
170
+ all_conditions_met = False
171
+ break
172
+
173
+ except (TypeError, Exception) as e:
174
+ structlogger.error(
175
+ "mcp_task_agent.is_exit_conditions_met.predicate.error",
176
+ predicate=condition,
177
+ error=str(e),
178
+ )
179
+ all_conditions_met = False
180
+ internal_error = str(e)
181
+ break
182
+
183
+ structlogger.debug(
184
+ "mcp_task_agent.is_exit_conditions_met.result",
185
+ all_conditions_met=all_conditions_met,
186
+ internal_error=internal_error,
187
+ )
188
+
189
+ return all_conditions_met, internal_error
190
+
191
+ def _get_slot_name_from_tool_name(self, tool_name: str) -> Optional[str]:
192
+ """Get the slot name from the tool name."""
193
+ match = re.match(r"^set_slot_(\w+)$", tool_name)
194
+ if match:
195
+ return match.group(1)
196
+ return None
197
+
198
+ def _run_set_slot_tool(
199
+ self, slot_name: str, arguments: Dict[str, Any]
200
+ ) -> Dict[str, Any]:
201
+ """Run the set slot tool."""
202
+ slot_value = arguments.get("slot_value")
203
+
204
+ # Handle type conversion for common cases
205
+ if isinstance(slot_value, str):
206
+ # Convert common boolean strings to actual booleans
207
+ if slot_value.lower() == "true":
208
+ slot_value = True
209
+ elif slot_value.lower() == "false":
210
+ slot_value = False
211
+
212
+ return {slot_name: slot_value}
213
+
214
+ def _generate_agent_task_completed_output(
215
+ self,
216
+ agent_input: AgentInput,
217
+ slots: Dict[str, Any],
218
+ tool_results: Dict[str, AgentToolResult],
219
+ ) -> AgentOutput:
220
+ """Generate an agent task completed output."""
221
+ _slot_names_to_be_filled = self._get_slot_names_from_exit_conditions(
222
+ agent_input
223
+ )
224
+ return AgentOutput(
225
+ id=agent_input.id,
226
+ status=AgentStatus.COMPLETED,
227
+ events=[
228
+ SlotSet(slot_name, slot_value)
229
+ for slot_name, slot_value in slots.items()
230
+ if slot_name in _slot_names_to_be_filled
231
+ ],
232
+ tool_results=self._get_tool_results_for_agent_output(
233
+ agent_input, tool_results
234
+ ),
235
+ )
236
+
237
+ async def send_message(self, agent_input: AgentInput) -> AgentOutput:
238
+ """Send a message to the LLM and return the response."""
239
+ messages = self.build_messages_for_llm_request(agent_input)
240
+ tool_results: Dict[str, AgentToolResult] = {}
241
+
242
+ _slots = agent_input.slots.copy()
243
+ _available_tools = self.get_available_tools(agent_input)
244
+ _available_tools_names = [tool.name for tool in _available_tools]
245
+
246
+ # Convert available tools to OpenAI JSON format
247
+ tools_in_openai_format = [
248
+ tool.to_openai_json_format() for tool in _available_tools
249
+ ]
250
+
251
+ for iteration in range(self.MAX_ITERATIONS):
252
+ try:
253
+ # Make the LLM call using the llm_client
254
+ structlogger.info(
255
+ "mcp_task_agent.send_message.sending_message_to_llm",
256
+ messages=messages,
257
+ event_info=f"Sending message to LLM (iteration {iteration + 1})",
258
+ agent_name=self._name,
259
+ agent_id=str(make_agent_identifier(self._name, self.protocol_type)),
260
+ )
261
+ llm_response = LLMResponse.ensure_llm_response(
262
+ await self.llm_client.acompletion(
263
+ messages, tools=tools_in_openai_format
264
+ )
265
+ )
266
+
267
+ # If no response from LLM, return an error output.
268
+ if llm_response is None or not (
269
+ llm_response.choices or llm_response.tool_calls
270
+ ):
271
+ event_info = "No response from LLM."
272
+ structlogger.warning(
273
+ "mcp_task_agent.send_message.no_llm_response",
274
+ event_info=event_info,
275
+ agent_name=self._name,
276
+ agent_id=str(
277
+ make_agent_identifier(self._name, self.protocol_type)
278
+ ),
279
+ )
280
+ return AgentOutput(
281
+ id=agent_input.id,
282
+ status=AgentStatus.RECOVERABLE_ERROR,
283
+ error_message=event_info,
284
+ tool_results=self._get_tool_results_for_agent_output(
285
+ agent_input, tool_results
286
+ ),
287
+ )
288
+
289
+ # If no tool calls, return the response directly with input required.
290
+ if not llm_response.tool_calls and len(llm_response.choices) == 1:
291
+ return AgentOutput(
292
+ id=agent_input.id,
293
+ status=AgentStatus.INPUT_REQUIRED,
294
+ response_message=llm_response.choices[0],
295
+ tool_results=self._get_tool_results_for_agent_output(
296
+ agent_input, tool_results
297
+ ),
298
+ )
299
+
300
+ # If there are tool calls, process them.
301
+ if llm_response.tool_calls:
302
+ # Add the assistant message with tool calls to the messages.
303
+ messages.append(
304
+ self._get_assistant_message_with_tool_calls(llm_response)
305
+ )
306
+ for tool_call in llm_response.tool_calls:
307
+ structlogger.info(
308
+ "mcp_task_agent.send_message.tool_call",
309
+ event_info=(
310
+ f"Processing tool call `{tool_call.tool_name}` with "
311
+ f"args: {tool_call.tool_args}"
312
+ ),
313
+ tool_name=tool_call.tool_name,
314
+ tool_args=json.dumps(tool_call.tool_args),
315
+ agent_name=self._name,
316
+ agent_id=str(
317
+ make_agent_identifier(self._name, self.protocol_type)
318
+ ),
319
+ )
320
+
321
+ # If the tool is not available, return a fatal error output.
322
+ if tool_call.tool_name not in _available_tools_names:
323
+ event_info = f"Tool {tool_call.tool_name} is not available."
324
+ structlogger.error(
325
+ "mcp_task_agent.send_message.tool_not_available",
326
+ tool_name=tool_call.tool_name,
327
+ event_info=event_info,
328
+ user_message=agent_input.user_message,
329
+ )
330
+ return AgentOutput(
331
+ id=agent_input.id,
332
+ status=AgentStatus.FATAL_ERROR,
333
+ error_message=event_info,
334
+ )
335
+
336
+ if slot_name := self._get_slot_name_from_tool_name(
337
+ tool_call.tool_name
338
+ ):
339
+ if (
340
+ slot_name in agent_input.slots
341
+ and "slot_value" in tool_call.tool_args
342
+ ):
343
+ _slots.update(
344
+ self._run_set_slot_tool(
345
+ slot_name, tool_call.tool_args
346
+ )
347
+ )
348
+
349
+ # Add the tool call message to the messages for
350
+ # slot-setting tools
351
+ messages.append(
352
+ {
353
+ KEY_ROLE: ROLE_TOOL,
354
+ KEY_TOOL_CALL_ID: tool_call.id,
355
+ KEY_CONTENT: f"Slot {slot_name} set to "
356
+ f"{tool_call.tool_args.get('slot_value')}",
357
+ }
358
+ )
359
+ else:
360
+ return AgentOutput(
361
+ id=agent_input.id,
362
+ status=AgentStatus.FATAL_ERROR,
363
+ error_message=(
364
+ f"The slot `{slot_name}` that the tool "
365
+ f"`{tool_call.tool_name}` is trying to set "
366
+ f"is not found in agent input."
367
+ ),
368
+ )
369
+ else:
370
+ # Execute the tool call.
371
+ tool_output = await self._execute_tool_call(
372
+ tool_call.tool_name,
373
+ tool_call.tool_args,
374
+ )
375
+
376
+ # If the tool call failed, generate an agent error output.
377
+ if tool_output.is_error or tool_output.result is None:
378
+ return self._generate_agent_error_output(
379
+ tool_output, agent_input, tool_call
380
+ )
381
+
382
+ # Store the tool output in the tool_results.
383
+ tool_results[tool_call.id] = tool_output
384
+
385
+ # Add the tool call message to the messages.
386
+ messages.append(
387
+ {
388
+ KEY_ROLE: ROLE_TOOL,
389
+ KEY_TOOL_CALL_ID: tool_call.id,
390
+ KEY_CONTENT: tool_output.result,
391
+ }
392
+ )
393
+
394
+ exit_met, internal_error = self._is_exit_conditions_met(
395
+ agent_input, _slots
396
+ )
397
+
398
+ # Agent signals task completion if exit conditions are met.
399
+ if exit_met:
400
+ return self._generate_agent_task_completed_output(
401
+ agent_input, _slots, tool_results
402
+ )
403
+
404
+ # If an internal error occurred while checking the exit
405
+ # conditions, return a fatal error output.
406
+ if internal_error:
407
+ return AgentOutput(
408
+ id=agent_input.id,
409
+ status=AgentStatus.FATAL_ERROR,
410
+ response_message=(
411
+ "An internal error occurred while checking the "
412
+ "exit conditions."
413
+ ),
414
+ tool_results=self._get_tool_results_for_agent_output(
415
+ agent_input, tool_results
416
+ ),
417
+ error_message=internal_error,
418
+ )
419
+
420
+ except Exception as e:
421
+ structlogger.error(
422
+ "mcp_task_agent.send_message.error_in_agent_loop",
423
+ event_info=f"Failed to send message: {e}",
424
+ user_message=agent_input.user_message,
425
+ agent_name=self._name,
426
+ agent_id=str(make_agent_identifier(self._name, self.protocol_type)),
427
+ )
428
+ return AgentOutput(
429
+ id=agent_input.id,
430
+ status=AgentStatus.FATAL_ERROR,
431
+ response_message=f"I encountered an error: {e!s}",
432
+ tool_results=self._get_tool_results_for_agent_output(
433
+ agent_input, tool_results
434
+ ),
435
+ error_message=str(e),
436
+ )
437
+ return AgentOutput(
438
+ id=agent_input.id,
439
+ status=AgentStatus.COMPLETED,
440
+ response_message=(
441
+ "I've completed my research but couldn't provide a final answer within"
442
+ "the allowed steps."
443
+ ),
444
+ tool_results=self._get_tool_results_for_agent_output(
445
+ agent_input, tool_results
446
+ ),
447
+ )
@@ -0,0 +1,6 @@
1
+ from rasa.agents.schemas.agent_input import AgentInput
2
+ from rasa.agents.schemas.agent_output import AgentOutput
3
+ from rasa.agents.schemas.agent_tool_result import AgentToolResult
4
+ from rasa.agents.schemas.agent_tool_schema import AgentToolSchema
5
+
6
+ __all__ = ["AgentInput", "AgentOutput", "AgentToolSchema", "AgentToolResult"]
@@ -0,0 +1,24 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from rasa.shared.core.events import Event
6
+
7
+
8
+ class AgentInput(BaseModel):
9
+ """A class that represents the schema of the input to the agent."""
10
+
11
+ id: str
12
+ user_message: str
13
+ slots: Dict[str, Any]
14
+ conversation_history: str
15
+ events: List[Event]
16
+ metadata: Dict[str, Any]
17
+ timestamp: Optional[str] = None
18
+
19
+ class Config:
20
+ """Skip validation for Event class as pydantic does not know how to
21
+ serialize or handle instances of the class.
22
+ """
23
+
24
+ arbitrary_types_allowed = True
@@ -0,0 +1,26 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from rasa.agents.core.types import AgentStatus
6
+ from rasa.shared.core.events import SlotSet
7
+
8
+
9
+ class AgentOutput(BaseModel):
10
+ """A class that represents the schema of the output from the agent."""
11
+
12
+ id: str
13
+ status: AgentStatus
14
+ response_message: Optional[str] = None
15
+ events: Optional[List[SlotSet]] = None
16
+ tool_results: Optional[List[List[Dict[str, Any]]]] = None
17
+ metadata: Optional[Dict[str, Any]] = None
18
+ timestamp: Optional[str] = None
19
+ error_message: Optional[str] = None
20
+
21
+ class Config:
22
+ """Skip validation for SlotSet class as pydantic does not know how to
23
+ serialize or handle instances of the class.
24
+ """
25
+
26
+ arbitrary_types_allowed = True
@@ -0,0 +1,51 @@
1
+ import json
2
+ from typing import Optional
3
+
4
+ from mcp.types import CallToolResult
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class AgentToolResult(BaseModel):
9
+ tool_name: str
10
+ result: Optional[str] = None
11
+ is_error: bool = False
12
+ error_message: Optional[str] = None
13
+
14
+ @classmethod
15
+ def from_mcp_tool_result(
16
+ cls, tool_name: str, tool_result: CallToolResult
17
+ ) -> "AgentToolResult":
18
+ if tool_result.isError:
19
+ return cls(
20
+ tool_name=tool_name,
21
+ result=None,
22
+ is_error=tool_result.isError,
23
+ error_message=str(tool_result.content),
24
+ )
25
+
26
+ # try to use structured content if available
27
+ if tool_result.structuredContent:
28
+ return cls(
29
+ tool_name=tool_name,
30
+ result=json.dumps(tool_result.structuredContent),
31
+ is_error=tool_result.isError,
32
+ )
33
+ # fallback to content if structured content is not available
34
+ elif tool_result.content:
35
+ # TODO try to parse the content using the output schema defined
36
+ # in the tool schema (if present)
37
+ return cls(
38
+ tool_name=tool_name,
39
+ result=json.dumps(
40
+ [content.model_dump() for content in tool_result.content]
41
+ ),
42
+ is_error=tool_result.isError,
43
+ )
44
+ # if no content is available, return None
45
+ else:
46
+ return cls(
47
+ tool_name=tool_name,
48
+ result=None,
49
+ is_error=tool_result.isError,
50
+ error_message="No content returned from tool `{tool_name}`",
51
+ )
@@ -0,0 +1,112 @@
1
+ from typing import Any, Dict, Optional
2
+
3
+ from mcp import Tool
4
+ from pydantic import BaseModel
5
+
6
+ from rasa.agents.constants import (
7
+ TOOL_ADDITIONAL_PROPERTIES_KEY,
8
+ TOOL_DESCRIPTION_KEY,
9
+ TOOL_NAME_KEY,
10
+ TOOL_PARAMETERS_KEY,
11
+ TOOL_PROPERTIES_KEY,
12
+ TOOL_PROPERTY_TYPE_KEY,
13
+ TOOL_REQUIRED_KEY,
14
+ TOOL_STRICT_KEY,
15
+ TOOL_TYPE_FUNCTION_KEY,
16
+ TOOL_TYPE_KEY,
17
+ )
18
+
19
+
20
+ class AgentToolSchema(BaseModel):
21
+ name: str
22
+ parameters: dict[str, Any]
23
+ strict: bool
24
+ description: Optional[str] = None
25
+ type: str = "function"
26
+
27
+ @classmethod
28
+ def from_mcp_tool(cls, tool: Tool) -> "AgentToolSchema":
29
+ """Convert MCP Tool to AgentToolSchema."""
30
+ parameters = tool.inputSchema.copy() if tool.inputSchema else {}
31
+
32
+ if parameters:
33
+ cls._validate_and_fix_parameters(parameters)
34
+
35
+ return cls(
36
+ name=tool.name,
37
+ description=tool.description,
38
+ parameters=parameters,
39
+ strict=False,
40
+ type=TOOL_TYPE_FUNCTION_KEY,
41
+ )
42
+
43
+ @classmethod
44
+ def from_openai_json_format(cls, tool: Dict[str, Any]) -> "AgentToolSchema":
45
+ """Convert OpenAI dict format to AgentToolSchema."""
46
+ function_data = tool[TOOL_TYPE_FUNCTION_KEY]
47
+ parameters = function_data.get(TOOL_PARAMETERS_KEY, {})
48
+
49
+ if parameters:
50
+ cls._validate_and_fix_parameters(parameters)
51
+
52
+ return cls(
53
+ name=function_data[TOOL_NAME_KEY],
54
+ description=function_data[TOOL_DESCRIPTION_KEY],
55
+ parameters=parameters,
56
+ strict=function_data.get(TOOL_STRICT_KEY, False),
57
+ type=tool[TOOL_TYPE_KEY],
58
+ )
59
+
60
+ @staticmethod
61
+ def _validate_and_fix_parameters(parameters: Dict[str, Any]) -> None:
62
+ """Validate and fix parameters to ensure they meet OpenAI function calling
63
+ requirements."""
64
+ if not parameters:
65
+ return
66
+
67
+ # Ensure additionalProperties is set at the top level
68
+ if TOOL_ADDITIONAL_PROPERTIES_KEY not in parameters:
69
+ parameters[TOOL_ADDITIONAL_PROPERTIES_KEY] = False
70
+
71
+ # Ensure required is set at the top level
72
+ if TOOL_REQUIRED_KEY not in parameters:
73
+ parameters[TOOL_REQUIRED_KEY] = []
74
+
75
+ # Ensure all properties have types, required field and additionalProperties
76
+ if TOOL_PROPERTIES_KEY in parameters:
77
+ AgentToolSchema._ensure_property_types(parameters)
78
+
79
+ @staticmethod
80
+ def _ensure_property_types(parameters: Dict[str, Any]) -> None:
81
+ """Ensure all properties in parameters have a type defined and
82
+ additionalProperties is set."""
83
+ properties = parameters[TOOL_PROPERTIES_KEY]
84
+
85
+ if not properties:
86
+ return
87
+
88
+ for _, prop_schema in properties.items():
89
+ if not isinstance(prop_schema, dict):
90
+ continue
91
+
92
+ # Ensure the property has a type
93
+ if TOOL_PROPERTY_TYPE_KEY not in prop_schema:
94
+ prop_schema[TOOL_PROPERTY_TYPE_KEY] = "string"
95
+
96
+ # If it's an object type, ensure additionalProperties and required
97
+ # fields are set
98
+ if prop_schema[TOOL_PROPERTY_TYPE_KEY] == "object":
99
+ if TOOL_ADDITIONAL_PROPERTIES_KEY not in prop_schema:
100
+ prop_schema[TOOL_ADDITIONAL_PROPERTIES_KEY] = False
101
+
102
+ # Ensure required key exists for object properties
103
+ if TOOL_REQUIRED_KEY not in prop_schema:
104
+ prop_schema[TOOL_REQUIRED_KEY] = []
105
+
106
+ def to_openai_json_format(self) -> Dict[str, Any]:
107
+ """Convert AgentToolSchema to OpenAI format."""
108
+ # Ensure the schema is valid before conversion
109
+ return {
110
+ TOOL_TYPE_KEY: TOOL_TYPE_FUNCTION_KEY,
111
+ TOOL_TYPE_FUNCTION_KEY: self.model_dump(exclude={TOOL_TYPE_KEY}),
112
+ }
File without changes
@@ -0,0 +1,15 @@
1
+ You are a helpful assistant that should assist the user in the best possible way.
2
+
3
+ ### Primary Task
4
+ {{ description }}
5
+
6
+ ### Instructions
7
+ * Always make sure to output responses to the user in a clear, helpful format.
8
+ * Always avoid asking multiple questions at once. Ask questions sequentially one at a time and wait for the user's response before proceeding to next question.
9
+ * Always avoid making assumptions about what values to pass into tools. Ask for clarification if a user's request is ambiguous.
10
+ * Once your primary task, described above, is completed, you must call the `task_completed` tool to signal that you have finished assisting the user. Do not end the conversation or indicate completion without the `task_completed` tool call.
11
+ * Do NOT call the `task_completed` tool unless you're absolutely certain that your primary task (NOT the slot corrections, updates, etc), described above, is fully completed. Focus on the task given in the task description above.
12
+ * Strictly avoid making up information or ability to take some action which is not available in `tool` provided.
13
+
14
+ ### Conversation history
15
+ {{ conversation_history }}