rasa-pro 3.14.0rc4__py3-none-any.whl → 3.15.0a1__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 (65) hide show
  1. rasa/agents/agent_manager.py +7 -5
  2. rasa/agents/protocol/a2a/a2a_agent.py +13 -11
  3. rasa/agents/protocol/mcp/mcp_base_agent.py +49 -11
  4. rasa/agents/validation.py +4 -2
  5. rasa/builder/config.py +4 -0
  6. rasa/builder/copilot/copilot.py +28 -9
  7. rasa/builder/copilot/copilot_templated_message_provider.py +1 -1
  8. rasa/builder/copilot/models.py +171 -4
  9. rasa/builder/document_retrieval/inkeep_document_retrieval.py +2 -0
  10. rasa/builder/download.py +1 -1
  11. rasa/builder/service.py +101 -24
  12. rasa/builder/telemetry/__init__.py +0 -0
  13. rasa/builder/telemetry/copilot_langfuse_telemetry.py +384 -0
  14. rasa/builder/{copilot/telemetry.py → telemetry/copilot_segment_telemetry.py} +21 -3
  15. rasa/builder/validation_service.py +4 -0
  16. rasa/cli/arguments/data.py +9 -0
  17. rasa/cli/data.py +72 -6
  18. rasa/cli/interactive.py +3 -0
  19. rasa/cli/llm_fine_tuning.py +1 -0
  20. rasa/cli/project_templates/defaults.py +1 -0
  21. rasa/cli/validation/bot_config.py +2 -0
  22. rasa/constants.py +2 -1
  23. rasa/core/actions/action_exceptions.py +1 -1
  24. rasa/core/agent.py +4 -1
  25. rasa/core/available_agents.py +1 -1
  26. rasa/core/exceptions.py +1 -1
  27. rasa/core/featurizers/tracker_featurizers.py +3 -2
  28. rasa/core/persistor.py +7 -7
  29. rasa/core/policies/flows/agent_executor.py +84 -4
  30. rasa/core/policies/flows/flow_exceptions.py +5 -2
  31. rasa/core/policies/flows/flow_executor.py +23 -8
  32. rasa/core/policies/flows/mcp_tool_executor.py +7 -1
  33. rasa/core/policies/rule_policy.py +1 -1
  34. rasa/core/run.py +15 -4
  35. rasa/dialogue_understanding/commands/cancel_flow_command.py +1 -1
  36. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +1 -1
  37. rasa/e2e_test/e2e_config.py +4 -3
  38. rasa/engine/recipes/default_components.py +16 -6
  39. rasa/graph_components/validators/default_recipe_validator.py +10 -4
  40. rasa/model_manager/runner_service.py +1 -1
  41. rasa/nlu/classifiers/diet_classifier.py +2 -0
  42. rasa/privacy/privacy_config.py +1 -1
  43. rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +4 -7
  44. rasa/shared/core/slots.py +55 -24
  45. rasa/shared/core/training_data/story_reader/story_reader.py +1 -1
  46. rasa/shared/exceptions.py +23 -2
  47. rasa/shared/providers/llm/litellm_router_llm_client.py +2 -2
  48. rasa/shared/utils/common.py +9 -1
  49. rasa/shared/utils/llm.py +21 -4
  50. rasa/shared/utils/mcp/server_connection.py +7 -4
  51. rasa/studio/download.py +3 -0
  52. rasa/studio/prompts.py +1 -0
  53. rasa/studio/upload.py +4 -0
  54. rasa/utils/common.py +9 -0
  55. rasa/utils/endpoints.py +2 -0
  56. rasa/utils/installation_utils.py +111 -0
  57. rasa/utils/log_utils.py +20 -1
  58. rasa/utils/tensorflow/callback.py +2 -0
  59. rasa/utils/train_utils.py +2 -0
  60. rasa/version.py +1 -1
  61. {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.15.0a1.dist-info}/METADATA +4 -2
  62. {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.15.0a1.dist-info}/RECORD +65 -62
  63. {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.15.0a1.dist-info}/NOTICE +0 -0
  64. {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.15.0a1.dist-info}/WHEEL +0 -0
  65. {rasa_pro-3.14.0rc4.dist-info → rasa_pro-3.15.0a1.dist-info}/entry_points.txt +0 -0
rasa/core/run.py CHANGED
@@ -328,10 +328,21 @@ def serve_application(
328
328
 
329
329
  logger.info(f"Starting Rasa server on {protocol}://{interface}:{port}")
330
330
 
331
- app.register_listener(
332
- partial(load_agent_on_start, model_path, endpoints, remote_storage, sub_agents),
333
- "before_server_start",
334
- )
331
+ async def load_agent_and_check_failure(app: Sanic, loop: AbstractEventLoop) -> None:
332
+ """Load agent and exit if it fails in non-debug mode."""
333
+ try:
334
+ await load_agent_on_start(
335
+ model_path, endpoints, remote_storage, sub_agents, app, loop
336
+ )
337
+ except Exception as e:
338
+ is_debug = logger.isEnabledFor(logging.DEBUG)
339
+ if is_debug:
340
+ raise e # show traceback in debug
341
+ # non-debug: log and exit without starting server
342
+ logger.error(f"Failed to load agent: {e}")
343
+ os._exit(1) # Any other exit method would show a traceback.
344
+
345
+ app.register_listener(load_agent_and_check_failure, "before_server_start")
335
346
 
336
347
  app.register_listener(
337
348
  licensing.validate_limited_server_license, "after_server_start"
@@ -56,7 +56,7 @@ class CancelFlowCommand(Command):
56
56
  Returns:
57
57
  The frames that were canceled.
58
58
  """
59
- canceled_frames = []
59
+ canceled_frames: List[str] = []
60
60
  # we need to go through the original stack dump in reverse order
61
61
  # to find the frames that were canceled. we cancel everything from
62
62
  # the top of the stack until we hit the user flow that was canceled.
@@ -233,7 +233,7 @@ flows:
233
233
  collect: interrupted_flow_to_continue
234
234
  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'."
235
235
  next:
236
- - if: slots.interrupted_flow_to_continue != "none"
236
+ - if: slots.interrupted_flow_to_continue is not "none"
237
237
  then:
238
238
  - action: action_continue_interrupted_flow
239
239
  next: END
@@ -72,9 +72,10 @@ class LLMJudgeConfig(BaseModel):
72
72
 
73
73
  llm_config = resolve_model_client_config(llm_config)
74
74
  llm_config, llm_extra_parameters = cls.extract_attributes(llm_config)
75
- llm_config = combine_custom_and_default_config(
76
- llm_config, cls.get_default_llm_config()
77
- )
75
+ if not llm_config:
76
+ llm_config = combine_custom_and_default_config(
77
+ llm_config, cls.get_default_llm_config()
78
+ )
78
79
  embeddings_config = resolve_model_client_config(embeddings)
79
80
  embeddings_config, embeddings_extra_parameters = cls.extract_attributes(
80
81
  embeddings_config
@@ -27,22 +27,32 @@ from rasa.shared.utils.common import conditional_import
27
27
 
28
28
  # components dependent on tensorflow
29
29
  TEDPolicy, TED_POLICY_AVAILABLE = conditional_import(
30
- "rasa.core.policies.ted_policy", "TEDPolicy"
30
+ "rasa.core.policies.ted_policy", "TEDPolicy", check_installation_setup=True
31
31
  )
32
32
  UnexpecTEDIntentPolicy, UNEXPECTED_INTENT_POLICY_AVAILABLE = conditional_import(
33
- "rasa.core.policies.unexpected_intent_policy", "UnexpecTEDIntentPolicy"
33
+ "rasa.core.policies.unexpected_intent_policy",
34
+ "UnexpecTEDIntentPolicy",
35
+ check_installation_setup=True,
34
36
  )
35
37
  DIETClassifier, DIET_CLASSIFIER_AVAILABLE = conditional_import(
36
- "rasa.nlu.classifiers.diet_classifier", "DIETClassifier"
38
+ "rasa.nlu.classifiers.diet_classifier",
39
+ "DIETClassifier",
40
+ check_installation_setup=True,
37
41
  )
38
42
  ConveRTFeaturizer, CONVERT_FEATURIZER_AVAILABLE = conditional_import(
39
- "rasa.nlu.featurizers.dense_featurizer.convert_featurizer", "ConveRTFeaturizer"
43
+ "rasa.nlu.featurizers.dense_featurizer.convert_featurizer",
44
+ "ConveRTFeaturizer",
45
+ check_installation_setup=True,
40
46
  )
41
47
  LanguageModelFeaturizer, LANGUAGE_MODEL_FEATURIZER_AVAILABLE = conditional_import(
42
- "rasa.nlu.featurizers.dense_featurizer.lm_featurizer", "LanguageModelFeaturizer"
48
+ "rasa.nlu.featurizers.dense_featurizer.lm_featurizer",
49
+ "LanguageModelFeaturizer",
50
+ check_installation_setup=True,
43
51
  )
44
52
  ResponseSelector, RESPONSE_SELECTOR_AVAILABLE = conditional_import(
45
- "rasa.nlu.selectors.response_selector", "ResponseSelector"
53
+ "rasa.nlu.selectors.response_selector",
54
+ "ResponseSelector",
55
+ check_installation_setup=True,
46
56
  )
47
57
 
48
58
  # components dependent on skops
@@ -40,16 +40,22 @@ from rasa.shared.utils.common import conditional_import
40
40
 
41
41
  # Conditional imports for TensorFlow-dependent components
42
42
  TEDPolicy, TED_POLICY_AVAILABLE = conditional_import(
43
- "rasa.core.policies.ted_policy", "TEDPolicy"
43
+ "rasa.core.policies.ted_policy", "TEDPolicy", check_installation_setup=True
44
44
  )
45
45
  UnexpecTEDIntentPolicy, UNEXPECTED_INTENT_POLICY_AVAILABLE = conditional_import(
46
- "rasa.core.policies.unexpected_intent_policy", "UnexpecTEDIntentPolicy"
46
+ "rasa.core.policies.unexpected_intent_policy",
47
+ "UnexpecTEDIntentPolicy",
48
+ check_installation_setup=True,
47
49
  )
48
50
  DIETClassifier, DIET_CLASSIFIER_AVAILABLE = conditional_import(
49
- "rasa.nlu.classifiers.diet_classifier", "DIETClassifier"
51
+ "rasa.nlu.classifiers.diet_classifier",
52
+ "DIETClassifier",
53
+ check_installation_setup=True,
50
54
  )
51
55
  ResponseSelector, RESPONSE_SELECTOR_AVAILABLE = conditional_import(
52
- "rasa.nlu.selectors.response_selector", "ResponseSelector"
56
+ "rasa.nlu.selectors.response_selector",
57
+ "ResponseSelector",
58
+ check_installation_setup=True,
53
59
  )
54
60
 
55
61
  # Conditional imports for nlu components requiring other dependencies than tensorflow
@@ -195,7 +195,7 @@ def fetch_remote_model_to_dir(
195
195
  try:
196
196
  return persistor.retrieve(model_name=model_name, target_path=target_path)
197
197
  except FileNotFoundError as e:
198
- raise ModelNotFound() from e
198
+ raise ModelNotFound("Model not found") from e
199
199
 
200
200
 
201
201
  def fetch_size_of_remote_model(
@@ -9,9 +9,11 @@ from typing import Any, Dict, List, Optional, Text, Tuple, Type, TypeVar, Union
9
9
  import numpy as np
10
10
  import scipy.sparse
11
11
 
12
+ from rasa.utils.installation_utils import check_for_installation_issues
12
13
  from rasa.utils.tensorflow import TENSORFLOW_AVAILABLE
13
14
 
14
15
  if TENSORFLOW_AVAILABLE:
16
+ check_for_installation_issues()
15
17
  import tensorflow as tf
16
18
  else:
17
19
  tf = None
@@ -211,7 +211,7 @@ def get_cron_trigger(cron_expression: str) -> CronTrigger:
211
211
  "privacy_config.invalid_cron_expression",
212
212
  cron=cron_expression,
213
213
  )
214
- raise RasaException from exc
214
+ raise RasaException("Invalid cron expression") from exc
215
215
 
216
216
  return cron
217
217
 
@@ -139,20 +139,17 @@ class OAuth2AuthStrategy(AgentAuthStrategy):
139
139
  resp.raise_for_status()
140
140
  token_data = resp.json()
141
141
  except httpx.HTTPStatusError as e:
142
- raise ValueError(
143
- f"OAuth2 token request failed with status {e.response.status_code}: "
144
- f"{e.response.text}"
145
- ) from e
142
+ raise e
146
143
  except httpx.RequestError as e:
147
- raise ValueError(f"OAuth2 token request failed: {e}") from e
144
+ raise ValueError(f"OAuth2 token request failed - {e}") from e
148
145
  except Exception as e:
149
146
  raise ValueError(
150
- f"Unexpected error during OAuth2 token request: {e}"
147
+ f"Unexpected error during OAuth2 token request - {e}"
151
148
  ) from e
152
149
 
153
150
  # Validate token data
154
151
  if KEY_ACCESS_TOKEN not in token_data:
155
- raise ValueError(f"No {KEY_ACCESS_TOKEN} in OAuth2 response")
152
+ raise ValueError(f"No `{KEY_ACCESS_TOKEN}` in OAuth2 response")
156
153
 
157
154
  # Set access token and expires at
158
155
  self._access_token = token_data[KEY_ACCESS_TOKEN]
rasa/shared/core/slots.py CHANGED
@@ -355,8 +355,8 @@ class FloatSlot(Slot):
355
355
  mappings: List[Dict[Text, Any]],
356
356
  initial_value: Optional[float] = None,
357
357
  value_reset_delay: Optional[int] = None,
358
- max_value: float = 1.0,
359
- min_value: float = 0.0,
358
+ max_value: Optional[float] = None,
359
+ min_value: Optional[float] = None,
360
360
  influence_conversation: bool = True,
361
361
  is_builtin: bool = False,
362
362
  shared_for_coexistence: bool = False,
@@ -380,32 +380,24 @@ class FloatSlot(Slot):
380
380
  filled_by=filled_by,
381
381
  validation=validation,
382
382
  )
383
+ self.validate_min_max_range(min_value, max_value)
384
+
383
385
  self.max_value = max_value
384
386
  self.min_value = min_value
385
387
 
386
- if min_value >= max_value:
387
- raise InvalidSlotConfigError(
388
- "Float slot ('{}') created with an invalid range "
389
- "using min ({}) and max ({}) values. Make sure "
390
- "min is smaller than max."
391
- "".format(self.name, self.min_value, self.max_value)
392
- )
393
-
394
- if initial_value is not None and not (min_value <= initial_value <= max_value):
395
- rasa.shared.utils.io.raise_warning(
396
- f"Float slot ('{self.name}') created with an initial value "
397
- f"{self.value}. This value is outside of the configured min "
398
- f"({self.min_value}) and max ({self.max_value}) values."
399
- )
400
-
401
388
  def _as_feature(self) -> List[float]:
389
+ # set default min and max values used in prior releases
390
+ # to prevent regressions for existing models
391
+ min_value = self.min_value or 0.0
392
+ max_value = self.max_value or 1.0
393
+
402
394
  try:
403
- capped_value = max(self.min_value, min(self.max_value, float(self.value)))
404
- if abs(self.max_value - self.min_value) > 0:
405
- covered_range = abs(self.max_value - self.min_value)
395
+ capped_value = max(min_value, min(max_value, float(self.value)))
396
+ if abs(max_value - min_value) > 0:
397
+ covered_range = abs(max_value - min_value)
406
398
  else:
407
399
  covered_range = 1
408
- return [1.0, (capped_value - self.min_value) / covered_range]
400
+ return [1.0, (capped_value - min_value) / covered_range]
409
401
  except (TypeError, ValueError):
410
402
  return [0.0, 0.0]
411
403
 
@@ -424,13 +416,52 @@ class FloatSlot(Slot):
424
416
  return value
425
417
 
426
418
  def is_valid_value(self, value: Any) -> bool:
427
- """Checks if the slot contains the value."""
428
- # check that coerced type is float
429
- return value is None or isinstance(self.coerce_value(value), float)
419
+ """Checks if the slot value is valid."""
420
+ if value is None:
421
+ return True
422
+
423
+ if not isinstance(self.coerce_value(value), float):
424
+ return False
425
+
426
+ if (
427
+ self.min_value is not None
428
+ and self.max_value is not None
429
+ and not (self.min_value <= value <= self.max_value)
430
+ ):
431
+ return False
432
+
433
+ return True
430
434
 
431
435
  def _feature_dimensionality(self) -> int:
432
436
  return len(self.as_feature())
433
437
 
438
+ def validate_min_max_range(
439
+ self, min_value: Optional[float], max_value: Optional[float]
440
+ ) -> None:
441
+ """Validates the min-max range for the slot.
442
+
443
+ Raises:
444
+ InvalidSlotConfigError, if the min-max range is invalid.
445
+ """
446
+ if min_value is not None and max_value is not None and min_value >= max_value:
447
+ raise InvalidSlotConfigError(
448
+ f"Float slot ('{self.name}') created with an invalid range "
449
+ f"using min ({min_value}) and max ({max_value}) values. Make sure "
450
+ f"min is smaller than max."
451
+ )
452
+
453
+ if (
454
+ self.initial_value is not None
455
+ and min_value is not None
456
+ and max_value is not None
457
+ and not (min_value <= self.initial_value <= max_value)
458
+ ):
459
+ raise InvalidSlotConfigError(
460
+ f"Float slot ('{self.name}') created with an initial value "
461
+ f"{self.initial_value}. This value is outside of the configured min "
462
+ f"({min_value}) and max ({max_value}) values."
463
+ )
464
+
434
465
 
435
466
  class BooleanSlot(Slot):
436
467
  """A slot storing a truth value."""
@@ -126,4 +126,4 @@ class StoryParseError(RasaCoreException, ValueError):
126
126
 
127
127
  def __init__(self, message: Text) -> None:
128
128
  self.message = message
129
- super(StoryParseError, self).__init__()
129
+ super(StoryParseError, self).__init__(message)
rasa/shared/exceptions.py CHANGED
@@ -16,6 +16,17 @@ class RasaException(Exception):
16
16
  to the users, but will be ignored in telemetry.
17
17
  """
18
18
 
19
+ def __init__(self, message: str, suppress_stack_trace: bool = False, **kwargs: Any):
20
+ """Initialize the exception.
21
+
22
+ Args:
23
+ message: The error message.
24
+ suppress_stack_trace: If True, the stack trace will be suppressed in logs.
25
+ **kwargs: Additional keyword arguments (e.g., cause for exception chaining).
26
+ """
27
+ Exception.__init__(self, message)
28
+ self.suppress_stack_trace = suppress_stack_trace
29
+
19
30
 
20
31
  class RasaCoreException(RasaException):
21
32
  """Basic exception for errors raised by Rasa Core."""
@@ -113,6 +124,17 @@ class SchemaValidationError(RasaException, jsonschema.ValidationError):
113
124
  class InvalidEntityFormatException(RasaException, json.JSONDecodeError):
114
125
  """Raised if the format of an entity is invalid."""
115
126
 
127
+ def __init__(self, msg: str, doc: str = "", pos: int = 0):
128
+ """Initialize the exception.
129
+
130
+ Args:
131
+ msg: The error message.
132
+ doc: The document that caused the error.
133
+ pos: The position in the document where the error occurred.
134
+ """
135
+ RasaException.__init__(self, msg)
136
+ json.JSONDecodeError.__init__(self, msg, doc, pos)
137
+
116
138
  @classmethod
117
139
  def create_from(
118
140
  cls, other: json.JSONDecodeError, msg: Text
@@ -130,8 +152,7 @@ class ConnectionException(RasaException):
130
152
 
131
153
 
132
154
  class ProviderClientAPIException(RasaException):
133
- """Raised for errors that occur during API interactions
134
- with LLM / embedding providers.
155
+ """For errors during API interactions with LLM / embedding providers.
135
156
 
136
157
  Attributes:
137
158
  original_exception (Exception): The original exception that was
@@ -151,7 +151,7 @@ class LiteLLMRouterLLMClient(_BaseLiteLLMRouterClient, _BaseLiteLLMClient):
151
151
  if not self._use_chat_completions_endpoint:
152
152
  return self._text_completion(messages)
153
153
  try:
154
- formatted_messages = self._format_messages(messages)
154
+ formatted_messages = self._get_formatted_messages(messages)
155
155
  response = self.router_client.completion(
156
156
  messages=formatted_messages, **{**self._completion_fn_args, **kwargs}
157
157
  )
@@ -189,7 +189,7 @@ class LiteLLMRouterLLMClient(_BaseLiteLLMRouterClient, _BaseLiteLLMClient):
189
189
  if not self._use_chat_completions_endpoint:
190
190
  return await self._atext_completion(messages)
191
191
  try:
192
- formatted_messages = self._format_messages(messages)
192
+ formatted_messages = self._get_formatted_messages(messages)
193
193
  response = await self.router_client.acompletion(
194
194
  messages=formatted_messages, **{**self._completion_fn_args, **kwargs}
195
195
  )
@@ -26,6 +26,7 @@ from rasa.exceptions import MissingDependencyException
26
26
  from rasa.shared.constants import DOCS_URL_MIGRATION_GUIDE
27
27
  from rasa.shared.exceptions import ProviderClientValidationError, RasaException
28
28
  from rasa.shared.utils.cli import print_success
29
+ from rasa.utils.installation_utils import check_for_installation_issues
29
30
 
30
31
  logger = logging.getLogger(__name__)
31
32
 
@@ -396,7 +397,11 @@ Sign up at: https://feedback.rasa.com
396
397
  print_success(message)
397
398
 
398
399
 
399
- def conditional_import(module_name: str, class_name: str) -> Tuple[Any, bool]:
400
+ def conditional_import(
401
+ module_name: str,
402
+ class_name: str,
403
+ check_installation_setup: bool = False,
404
+ ) -> Tuple[Any, bool]:
400
405
  """Conditionally import a class, returning (class, is_available) tuple.
401
406
 
402
407
  Args:
@@ -408,6 +413,9 @@ def conditional_import(module_name: str, class_name: str) -> Tuple[Any, bool]:
408
413
  or None if import failed, and is_available is a boolean indicating
409
414
  whether the import was successful.
410
415
  """
416
+ if check_installation_setup:
417
+ check_for_installation_issues()
418
+
411
419
  try:
412
420
  module = __import__(module_name, fromlist=[class_name])
413
421
  return getattr(module, class_name), True
rasa/shared/utils/llm.py CHANGED
@@ -1074,7 +1074,7 @@ def _get_llm_command_generator_config(
1074
1074
 
1075
1075
 
1076
1076
  def _get_compact_llm_command_generator_prompt(
1077
- config: Dict[Text, Any], endpoints: Dict[Text, Any]
1077
+ config: Dict[Text, Any], model_groups: Dict[Text, Any]
1078
1078
  ) -> Text:
1079
1079
  """Get the command generator prompt based on the config."""
1080
1080
  from rasa.dialogue_understanding.generator.single_step.compact_llm_command_generator import ( # noqa: E501
@@ -1084,7 +1084,7 @@ def _get_compact_llm_command_generator_prompt(
1084
1084
  model_config = _get_llm_command_generator_config(config)
1085
1085
  llm_config = resolve_model_client_config(
1086
1086
  model_config=model_config,
1087
- model_groups=endpoints.get(MODEL_GROUPS_CONFIG_KEY),
1087
+ model_groups=model_groups,
1088
1088
  )
1089
1089
  return get_default_prompt_template_based_on_model(
1090
1090
  llm_config=llm_config or {},
@@ -1119,7 +1119,7 @@ def get_system_default_prompts(
1119
1119
 
1120
1120
  Args:
1121
1121
  config: The config.yml file data.
1122
- endpoints: The endpoints.yml file data.
1122
+ endpoints: The endpoints configuration dictionary.
1123
1123
 
1124
1124
  Returns:
1125
1125
  SystemPrompts: A Pydantic model containing all default prompts.
@@ -1128,8 +1128,25 @@ def get_system_default_prompts(
1128
1128
  DEFAULT_RESPONSE_VARIATION_PROMPT_TEMPLATE,
1129
1129
  )
1130
1130
 
1131
+ # The Model Manager / Model API service receives the endpoints configuration
1132
+ # as raw YAML text rather than as a file path. However, both
1133
+ # Configuration.initialise_endpoints() and AvailableEndpoints.read_endpoints()
1134
+ # currently only accept a Path input and do not support loading from in-memory
1135
+ # YAML content.
1136
+
1137
+ # Since these classes only support file-based initialization today, we need
1138
+ # to bootstrap the Configuration with an empty AvailableEndpoints instance for now.
1139
+
1140
+ # IMPORTANT: This configuration must be properly initialized with valid endpoints
1141
+ # and available agents once Studio introduces full support for Agent configurations
1142
+ # (A2A and MCP).
1143
+ Configuration.initialise_empty()
1144
+
1145
+ model_groups = endpoints.get(MODEL_GROUPS_CONFIG_KEY)
1131
1146
  return SystemPrompts(
1132
- command_generator=_get_compact_llm_command_generator_prompt(config, endpoints),
1147
+ command_generator=_get_compact_llm_command_generator_prompt(
1148
+ config, model_groups
1149
+ ),
1133
1150
  enterprise_search=_get_enterprise_search_prompt(config),
1134
1151
  contextual_response_rephraser=DEFAULT_RESPONSE_VARIATION_PROMPT_TEMPLATE,
1135
1152
  )
@@ -121,7 +121,8 @@ class MCPServerConnection:
121
121
  except Exception as eg:
122
122
  for exc in getattr(eg, "exceptions", [eg]):
123
123
  event_info = (
124
- f"Failed to connect to MCP server `{self.server_name}`: {exc!s}"
124
+ f"Failed to connect to MCP server `{self.server_name}`. \nOriginal "
125
+ f"error: {exc!s}"
125
126
  )
126
127
  if isinstance(exc, HTTPStatusError):
127
128
  status_code = exc.response.status_code
@@ -135,9 +136,11 @@ class MCPServerConnection:
135
136
  )
136
137
  await self._cleanup()
137
138
  if status_code in [400, 401, 403]:
138
- raise AuthenticationError(eg) from eg
139
+ raise AuthenticationError(str(exc)) from eg
140
+ elif status_code == 404:
141
+ raise Exception(str(exc)) from eg
139
142
  else:
140
- raise ConnectionError(eg) from eg
143
+ raise ConnectionError(str(exc)) from eg
141
144
  else:
142
145
  structlogger.error(
143
146
  "mcp_server_connection.connect.other_exception",
@@ -147,7 +150,7 @@ class MCPServerConnection:
147
150
  error=str(exc),
148
151
  )
149
152
  await self._cleanup()
150
- raise ConnectionError(eg) from eg
153
+ raise ConnectionError(str(exc)) from eg
151
154
 
152
155
  except asyncio.CancelledError as e:
153
156
  event_info = f"Connection to MCP server `{self.server_name}` was cancelled."
rasa/studio/download.py CHANGED
@@ -10,6 +10,7 @@ from ruamel.yaml.scalarstring import LiteralScalarString
10
10
 
11
11
  import rasa.cli.utils
12
12
  import rasa.shared.utils.cli
13
+ from rasa.core.config.configuration import Configuration
13
14
  from rasa.shared.constants import (
14
15
  DEFAULT_CONFIG_PATH,
15
16
  DEFAULT_DATA_PATH,
@@ -116,6 +117,8 @@ def _handle_endpoints(handler: StudioDataHandler, root: Path) -> None:
116
117
  endpoints_path = root / DEFAULT_ENDPOINTS_PATH
117
118
  endpoints_path.write_text(endpoints_data, encoding="utf-8")
118
119
 
120
+ Configuration.initialise_endpoints(endpoints_path=endpoints_path)
121
+
119
122
 
120
123
  def _handle_domain(handler: StudioDataHandler, root: Path) -> None:
121
124
  """Persist the assistant’s domain file.
rasa/studio/prompts.py CHANGED
@@ -43,6 +43,7 @@ def handle_prompts(prompts: Dict[Text, Text], root: Path) -> None:
43
43
  config: Dict = read_yaml(config_path)
44
44
  endpoints: Dict = read_yaml(endpoints_path)
45
45
 
46
+ # System default prompts are dependent on the endpoints
46
47
  system_prompts = get_system_default_prompts(config, endpoints)
47
48
 
48
49
  _handle_contextual_response_rephraser(
rasa/studio/upload.py CHANGED
@@ -154,6 +154,10 @@ def handle_upload(args: argparse.Namespace) -> None:
154
154
  "Authentication is invalid or expired. Please run `rasa studio login`."
155
155
  )
156
156
 
157
+ from rasa.core.config.configuration import Configuration
158
+
159
+ Configuration.initialise_empty()
160
+
157
161
  structlogger.info("rasa.studio.upload.loading_data", event_info="Loading data...")
158
162
 
159
163
  args.domain = get_validated_path(args.domain, "domain", DEFAULT_DOMAIN_PATHS)
rasa/utils/common.py CHANGED
@@ -36,6 +36,7 @@ from rasa.constants import (
36
36
  ENV_LOG_LEVEL_LIBRARIES,
37
37
  ENV_LOG_LEVEL_MATPLOTLIB,
38
38
  ENV_LOG_LEVEL_MCP,
39
+ ENV_LOG_LEVEL_PYMONGO,
39
40
  ENV_LOG_LEVEL_RABBITMQ,
40
41
  ENV_MCP_LOGGING_ENABLED,
41
42
  )
@@ -297,6 +298,7 @@ def configure_library_logging() -> None:
297
298
  update_rabbitmq_log_level(library_log_level)
298
299
  update_websockets_log_level(library_log_level)
299
300
  update_mcp_log_level()
301
+ update_pymongo_log_level(library_log_level)
300
302
 
301
303
 
302
304
  def update_apscheduler_log_level() -> None:
@@ -481,6 +483,13 @@ def update_mcp_log_level() -> None:
481
483
  logging.getLogger(logger_name).propagate = False
482
484
 
483
485
 
486
+ def update_pymongo_log_level(library_log_level: str) -> None:
487
+ """Set the log level of pymongo."""
488
+ log_level = os.environ.get(ENV_LOG_LEVEL_PYMONGO, library_log_level)
489
+ logging.getLogger("pymongo").setLevel(log_level)
490
+ logging.getLogger("pymongo").propagate = False
491
+
492
+
484
493
  def sort_list_of_dicts_by_first_key(dicts: List[Dict]) -> List[Dict]:
485
494
  """Sorts a list of dictionaries by their first key."""
486
495
  return sorted(dicts, key=lambda d: next(iter(d.keys())))
rasa/utils/endpoints.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import ssl
3
+ from functools import lru_cache
3
4
  from pathlib import Path
4
5
  from types import ModuleType
5
6
  from typing import Any, Dict, List, Optional, Text, Union
@@ -16,6 +17,7 @@ from rasa.shared.utils.yaml import read_config_file
16
17
  structlogger = structlog.get_logger()
17
18
 
18
19
 
20
+ @lru_cache(maxsize=10)
19
21
  def read_endpoint_config(
20
22
  filename: Union[str, Path], endpoint_type: Text
21
23
  ) -> Optional["EndpointConfig"]: