rasa-pro 3.14.0rc2__py3-none-any.whl → 3.14.0rc4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rasa-pro might be problematic. Click here for more details.

Files changed (82) hide show
  1. rasa/agents/protocol/a2a/a2a_agent.py +50 -42
  2. rasa/agents/protocol/mcp/mcp_base_agent.py +15 -1
  3. rasa/agents/utils.py +27 -5
  4. rasa/agents/validation.py +64 -13
  5. rasa/api.py +1 -2
  6. rasa/builder/copilot/constants.py +4 -0
  7. rasa/builder/copilot/copilot.py +37 -1
  8. rasa/builder/copilot/copilot_templated_message_provider.py +23 -0
  9. rasa/builder/copilot/models.py +43 -49
  10. rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +33 -12
  11. rasa/builder/copilot/prompts/latest_user_message_context_prompt.jinja2 +59 -29
  12. rasa/builder/copilot/telemetry.py +8 -0
  13. rasa/builder/copilot/templated_messages/copilot_templated_responses.yml +3 -0
  14. rasa/builder/copilot/templated_messages/copilot_welcome_messages.yml +56 -0
  15. rasa/builder/jobs.py +162 -5
  16. rasa/builder/models.py +3 -0
  17. rasa/builder/service.py +1 -0
  18. rasa/cli/dialogue_understanding_test.py +1 -0
  19. rasa/cli/e2e_test.py +1 -0
  20. rasa/cli/inspect.py +1 -0
  21. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/feedback.yml +46 -0
  22. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/goodbye.yml +9 -0
  23. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/help.yml +8 -0
  24. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/human_handoff.yml +41 -0
  25. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/patterns.yml +32 -0
  26. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/show_faqs.yml +8 -0
  27. rasa/cli/project_templates/telco/data/network/flow_solve_internet_issue.yml +2 -2
  28. rasa/cli/project_templates/telco/domain/network/solve_internet_issue.yml +1 -2
  29. rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_not_slow.yml +33 -0
  30. rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_slow.yml +47 -0
  31. rasa/cli/project_templates/telco/tests/e2e_test_cases/without_stub/general/hello.yml +8 -0
  32. rasa/cli/run.py +1 -5
  33. rasa/cli/shell.py +1 -0
  34. rasa/cli/train.py +1 -0
  35. rasa/cli/validation/bot_config.py +9 -2
  36. rasa/core/available_agents.py +65 -55
  37. rasa/core/config/available_endpoints.py +6 -3
  38. rasa/core/config/configuration.py +36 -1
  39. rasa/core/policies/flows/agent_executor.py +16 -8
  40. rasa/dialogue_understanding/commands/start_flow_command.py +10 -3
  41. rasa/dialogue_understanding/commands/utils.py +15 -4
  42. rasa/dialogue_understanding/generator/llm_based_command_generator.py +4 -2
  43. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +4 -4
  44. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +4 -4
  45. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +2 -2
  46. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +22 -20
  47. rasa/dialogue_understanding_test/du_test_runner.py +2 -2
  48. rasa/e2e_test/e2e_test_runner.py +2 -2
  49. rasa/engine/validation.py +6 -2
  50. rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +10 -4
  51. rasa/shared/agents/auth/constants.py +1 -0
  52. rasa/shared/agents/auth/utils.py +85 -0
  53. rasa/shared/constants.py +3 -0
  54. rasa/shared/core/flows/steps/call.py +2 -2
  55. rasa/shared/utils/llm.py +33 -0
  56. rasa/telemetry.py +3 -3
  57. rasa/utils/pypred.py +7 -0
  58. rasa/validator.py +127 -2
  59. rasa/version.py +1 -1
  60. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc4.dist-info}/METADATA +19 -7
  61. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc4.dist-info}/RECORD +81 -71
  62. rasa/cli/project_templates/telco/tests/e2e_test_cases/network/solve_internet_issue.yml +0 -57
  63. /rasa/cli/project_templates/{finance/tests/e2e_test_cases → basic/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
  64. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/check_balance.yml +0 -0
  65. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/download_statements.yml +0 -0
  66. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{cards → without_stub/cards}/block_card.yml +0 -0
  67. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
  68. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
  69. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
  70. /rasa/cli/project_templates/{telco/tests/e2e_test_cases → finance/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
  71. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
  72. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
  73. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{transfers → without_stub/transfers}/transfer_money.yml +0 -0
  74. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{billing → without_stub/billing}/understand_bill.yml +0 -0
  75. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
  76. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
  77. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
  78. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
  79. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
  80. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc4.dist-info}/NOTICE +0 -0
  81. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc4.dist-info}/WHEEL +0 -0
  82. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc4.dist-info}/entry_points.txt +0 -0
@@ -104,7 +104,10 @@ class StartFlowCommand(Command):
104
104
  # predicted a start flow command for the flow which is on top of the stack,
105
105
  # we just need to remove the pattern_continue_interrupted frame(s) from the
106
106
  # stack
107
- stack = remove_pattern_continue_interrupted_frames(stack)
107
+ stack, flow_completed_events = remove_pattern_continue_interrupted_frames(
108
+ stack
109
+ )
110
+ applied_events.extend(flow_completed_events)
108
111
  return applied_events + tracker.create_stack_updated_events(stack)
109
112
 
110
113
  # if the flow is already on the stack, resume it
@@ -114,7 +117,10 @@ class StartFlowCommand(Command):
114
117
  ):
115
118
  # if pattern_continue_interrupted is active, we need to remove it
116
119
  # from the stack before resuming the flow
117
- stack = remove_pattern_continue_interrupted_frames(stack)
120
+ stack, flow_completed_events = remove_pattern_continue_interrupted_frames(
121
+ stack
122
+ )
123
+ applied_events.extend(flow_completed_events)
118
124
  applied_events.extend(resume_flow(self.flow, tracker, stack))
119
125
  # the current active flow is interrupted
120
126
  applied_events.append(
@@ -128,7 +134,8 @@ class StartFlowCommand(Command):
128
134
 
129
135
  # remove the pattern_continue_interrupted frames from the stack
130
136
  # if it is currently active but the user digressed from the pattern
131
- stack = remove_pattern_continue_interrupted_frames(stack)
137
+ stack, flow_completed_events = remove_pattern_continue_interrupted_frames(stack)
138
+ applied_events.extend(flow_completed_events)
132
139
 
133
140
  if original_top_flow:
134
141
  # if the original top flow is not the same as the flow to start,
@@ -19,6 +19,7 @@ from rasa.shared.constants import ACTION_ASK_PREFIX, UTTER_ASK_PREFIX
19
19
  from rasa.shared.core.events import (
20
20
  AgentResumed,
21
21
  Event,
22
+ FlowCompleted,
22
23
  FlowResumed,
23
24
  SlotSet,
24
25
  )
@@ -250,18 +251,28 @@ def collect_frames_to_resume(
250
251
  return list(frames_to_resume), frame_to_resume
251
252
 
252
253
 
253
- def remove_pattern_continue_interrupted_frames(stack: DialogueStack) -> DialogueStack:
254
- """Remove pattern_continue_interrupted frames from the stack."""
254
+ def remove_pattern_continue_interrupted_frames(
255
+ stack: DialogueStack,
256
+ ) -> Tuple[DialogueStack, List[FlowCompleted]]:
257
+ """Remove pattern_continue_interrupted frames from the stack and return events.
258
+
259
+ Returns:
260
+ A tuple containing (updated_stack, flow_completed_events)
261
+ """
255
262
  from rasa.dialogue_understanding.stack.utils import (
256
263
  is_continue_interrupted_flow_active,
257
264
  )
258
265
 
259
266
  if not is_continue_interrupted_flow_active(stack):
260
- return stack
267
+ return stack, []
261
268
 
269
+ events = []
262
270
  # remove pattern_continue_interrupted from the stack
263
271
  top_frame = stack.top()
264
272
  while isinstance(top_frame, PatternFlowStackFrame):
273
+ # Create FlowCompleted event for the pattern frame being removed
274
+ events.append(FlowCompleted(top_frame.flow_id, top_frame.step_id))
275
+
265
276
  # If the top frame is a pattern frame, we need to remove it
266
277
  # before continuing with the active user flow frame.
267
278
  # This prevents the pattern frame
@@ -271,4 +282,4 @@ def remove_pattern_continue_interrupted_frames(stack: DialogueStack) -> Dialogue
271
282
  stack.pop()
272
283
  top_frame = stack.top()
273
284
 
274
- return stack
285
+ return stack, events
@@ -7,7 +7,7 @@ from jinja2 import Environment, Template, select_autoescape
7
7
 
8
8
  import rasa.dialogue_understanding.generator.utils
9
9
  import rasa.shared.utils.io
10
- from rasa.core.available_agents import AvailableAgents
10
+ from rasa.core.config.configuration import Configuration
11
11
  from rasa.dialogue_understanding.commands import (
12
12
  Command,
13
13
  SetSlotCommand,
@@ -398,7 +398,9 @@ class LLMBasedCommandGenerator(
398
398
  if isinstance(event, AgentStarted) and event.flow_id == flow.id
399
399
  ]
400
400
  available_agents = [
401
- AvailableAgents.get_agent_config(event.agent_id)
401
+ Configuration.get_instance().available_agents.get_agent_config(
402
+ event.agent_id
403
+ )
402
404
  for event in agent_events
403
405
  ]
404
406
  if available_agents:
@@ -2,7 +2,7 @@ from typing import Any, Dict, Literal, Optional, Text
2
2
 
3
3
  import structlog
4
4
 
5
- from rasa.core.available_agents import AvailableAgents
5
+ from rasa.core.config.configuration import Configuration
6
6
  from rasa.dialogue_understanding.commands.command_syntax_manager import (
7
7
  CommandSyntaxVersion,
8
8
  )
@@ -125,7 +125,7 @@ class CompactLLMCommandGenerator(SingleStepBasedLLMCommandGenerator):
125
125
  """Get the default prompt template file name for the command generator."""
126
126
  return (
127
127
  AGENT_DEFAULT_COMMAND_PROMPT_TEMPLATE_FILE_NAME
128
- if AvailableAgents.has_agents()
128
+ if Configuration.get_instance().available_agents.has_agents()
129
129
  else DEFAULT_COMMAND_PROMPT_TEMPLATE_FILE_NAME
130
130
  )
131
131
 
@@ -134,7 +134,7 @@ class CompactLLMCommandGenerator(SingleStepBasedLLMCommandGenerator):
134
134
  """Get the fallback prompt template file name for the command generator."""
135
135
  return (
136
136
  AGENT_FALLBACK_COMMAND_PROMPT_TEMPLATE_FILE_NAME
137
- if AvailableAgents.has_agents()
137
+ if Configuration.get_instance().available_agents.has_agents()
138
138
  else FALLBACK_COMMAND_PROMPT_TEMPLATE_FILE_NAME
139
139
  )
140
140
 
@@ -143,7 +143,7 @@ class CompactLLMCommandGenerator(SingleStepBasedLLMCommandGenerator):
143
143
  """Get the model prompt mapper for the command generator."""
144
144
  return (
145
145
  AGENT_MODEL_PROMPT_MAPPER
146
- if AvailableAgents.has_agents()
146
+ if Configuration.get_instance().available_agents.has_agents()
147
147
  else MODEL_PROMPT_MAPPER
148
148
  )
149
149
 
@@ -2,7 +2,7 @@ from typing import Any, Dict, Literal, Optional, Text
2
2
 
3
3
  import structlog
4
4
 
5
- from rasa.core.available_agents import AvailableAgents
5
+ from rasa.core.config.configuration import Configuration
6
6
  from rasa.dialogue_understanding.commands.command_syntax_manager import (
7
7
  CommandSyntaxVersion,
8
8
  )
@@ -127,7 +127,7 @@ class SearchReadyLLMCommandGenerator(SingleStepBasedLLMCommandGenerator):
127
127
  """Get the default prompt template file name for the command generator."""
128
128
  return (
129
129
  AGENT_DEFAULT_COMMAND_PROMPT_TEMPLATE_FILE_NAME
130
- if AvailableAgents.has_agents()
130
+ if Configuration.get_instance().available_agents.has_agents()
131
131
  else DEFAULT_COMMAND_PROMPT_TEMPLATE_FILE_NAME
132
132
  )
133
133
 
@@ -136,7 +136,7 @@ class SearchReadyLLMCommandGenerator(SingleStepBasedLLMCommandGenerator):
136
136
  """Get the fallback prompt template file name for the command generator."""
137
137
  return (
138
138
  AGENT_FALLBACK_COMMAND_PROMPT_TEMPLATE_FILE_NAME
139
- if AvailableAgents.has_agents()
139
+ if Configuration.get_instance().available_agents.has_agents()
140
140
  else FALLBACK_COMMAND_PROMPT_TEMPLATE_FILE_NAME
141
141
  )
142
142
 
@@ -145,7 +145,7 @@ class SearchReadyLLMCommandGenerator(SingleStepBasedLLMCommandGenerator):
145
145
  """Get the model prompt mapper for the command generator."""
146
146
  return (
147
147
  AGENT_MODEL_PROMPT_MAPPER
148
- if AvailableAgents.has_agents()
148
+ if Configuration.get_instance().available_agents.has_agents()
149
149
  else MODEL_PROMPT_MAPPER
150
150
  )
151
151
 
@@ -9,7 +9,7 @@ from rasa.agents.utils import (
9
9
  get_active_agent_info,
10
10
  get_completed_agents_info,
11
11
  )
12
- from rasa.core.available_agents import AvailableAgents
12
+ from rasa.core.config.configuration import Configuration
13
13
  from rasa.dialogue_understanding.commands import (
14
14
  CannotHandleCommand,
15
15
  Command,
@@ -403,7 +403,7 @@ class SingleStepBasedLLMCommandGenerator(LLMBasedCommandGenerator, ABC):
403
403
  current_slot_allowed_values = allowed_values_for_slot(
404
404
  tracker.slots.get(current_slot)
405
405
  )
406
- has_agents = AvailableAgents.has_agents()
406
+ has_agents = Configuration.get_instance().available_agents.has_agents()
407
407
  current_conversation = tracker_as_readable_transcript(
408
408
  tracker, highlight_agent_turns=has_agents
409
409
  )
@@ -227,28 +227,30 @@ flows:
227
227
  - noop: true
228
228
  next:
229
229
  - if: context.multiple_flows_interrupted
230
+ then: collect_interrupted_flow_to_continue
231
+ - else: collect_continue_interrupted_flow_confirmation
232
+ - id: collect_interrupted_flow_to_continue
233
+ collect: interrupted_flow_to_continue
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
+ next:
236
+ - if: slots.interrupted_flow_to_continue != "none"
237
+ then:
238
+ - action: action_continue_interrupted_flow
239
+ next: END
240
+ - else:
241
+ - action: action_cancel_interrupted_flows
242
+ next: END
243
+ - id: collect_continue_interrupted_flow_confirmation
244
+ collect: continue_interrupted_flow_confirmation
245
+ description: "If the user wants to continue the interrupted flow, fill this slot with true. If the user does not want to continue the interrupted flow, fill this slot with false."
246
+ next:
247
+ - if: slots.continue_interrupted_flow_confirmation
230
248
  then:
231
- - collect: interrupted_flow_to_continue
232
- description: "Fill this slot with the name of the flow the user wants to continue. If the user does not want to continue any of the interrupted flows, fill this slot with 'none'."
233
- next:
234
- - if: slots.interrupted_flow_to_continue != "none"
235
- then:
236
- - action: action_continue_interrupted_flow
237
- next: END
238
- - else:
239
- - action: action_cancel_interrupted_flows
240
- next: END
249
+ - action: action_continue_interrupted_flow
250
+ next: END
241
251
  - else:
242
- - collect: continue_interrupted_flow_confirmation
243
- description: "If the user wants to continue the interrupted flow, fill this slot with true. If the user does not want to continue the interrupted flow, fill this slot with false."
244
- next:
245
- - if: slots.continue_interrupted_flow_confirmation
246
- then:
247
- - action: action_continue_interrupted_flow
248
- next: END
249
- - else:
250
- - action: action_cancel_interrupted_flows
251
- next: END
252
+ - action: action_cancel_interrupted_flows
253
+ next: END
252
254
 
253
255
  pattern_correction:
254
256
  description: Conversation repair flow for managing user input changes or error corrections
@@ -6,9 +6,9 @@ from typing import Any, Dict, List, Optional, Union
6
6
  import structlog
7
7
  from tqdm import tqdm
8
8
 
9
- from rasa.core.available_agents import AvailableAgents
10
9
  from rasa.core.channels import CollectingOutputChannel, UserMessage
11
10
  from rasa.core.config.available_endpoints import AvailableEndpoints
11
+ from rasa.core.config.configuration import Configuration
12
12
  from rasa.core.exceptions import AgentNotReady
13
13
  from rasa.core.persistor import StorageType
14
14
  from rasa.dialogue_understanding.commands import Command
@@ -71,7 +71,7 @@ class DialogueUnderstandingTestRunner:
71
71
  import rasa.core.agent
72
72
 
73
73
  self._check_action_server(endpoints)
74
- sub_agents = AvailableAgents.get_instance(sub_agents_path)
74
+ sub_agents = Configuration.get_instance().available_agents
75
75
 
76
76
  self.agent = asyncio.run(
77
77
  rasa.core.agent.load_agent(
@@ -13,9 +13,9 @@ import structlog
13
13
  from tqdm import tqdm
14
14
 
15
15
  import rasa.shared.utils.io
16
- from rasa.core.available_agents import AvailableAgents
17
16
  from rasa.core.channels import CollectingOutputChannel, UserMessage
18
17
  from rasa.core.config.available_endpoints import AvailableEndpoints
18
+ from rasa.core.config.configuration import Configuration
19
19
  from rasa.core.constants import ACTIVE_FLOW_METADATA_KEY, STEP_ID_METADATA_KEY
20
20
  from rasa.core.exceptions import AgentNotReady
21
21
  from rasa.core.persistor import StorageType
@@ -96,7 +96,7 @@ class E2ETestRunner:
96
96
  if endpoints and not are_custom_actions_stubbed:
97
97
  self._action_server_is_reachable(endpoints)
98
98
 
99
- sub_agents = AvailableAgents.get_instance(sub_agents_path)
99
+ sub_agents = Configuration.get_instance().available_agents
100
100
 
101
101
  self.agent = asyncio.run(
102
102
  rasa.core.agent.load_agent(
rasa/engine/validation.py CHANGED
@@ -81,6 +81,7 @@ from rasa.shared.constants import (
81
81
  ROUTING_STRATEGIES_NOT_REQUIRING_CACHE,
82
82
  ROUTING_STRATEGIES_REQUIRING_REDIS_CACHE,
83
83
  ROUTING_STRATEGY_CONFIG_KEY,
84
+ SECRET_DATA_FORMAT_PATTERN,
84
85
  SENSITIVE_DATA,
85
86
  USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY,
86
87
  VALID_PROVIDERS_FOR_API_TYPE_CONFIG_KEY,
@@ -1359,7 +1360,10 @@ def _validate_usage_of_environment_variables_in_model_group_config(
1359
1360
  for model_config in model_group[MODELS_CONFIG_KEY]:
1360
1361
  for key, value in model_config.items():
1361
1362
  if isinstance(value, str):
1362
- if re.match(r"\${(\w+)}", value) and key not in allowed_env_vars:
1363
+ if (
1364
+ re.match(SECRET_DATA_FORMAT_PATTERN, value)
1365
+ and key not in allowed_env_vars
1366
+ ):
1363
1367
  raise ValidationError(
1364
1368
  code="engine.validation.validate_model_group_configuration_setup"
1365
1369
  ".invalid_use_of_environment_variables",
@@ -1386,7 +1390,7 @@ def _validate_sensitive_keys_are_an_environment_variables_for_model_groups(
1386
1390
  for key, value in model_config.items():
1387
1391
  if key in SENSITIVE_DATA:
1388
1392
  if isinstance(value, str):
1389
- if not re.match(r"\${(\w+)}", value):
1393
+ if not re.match(SECRET_DATA_FORMAT_PATTERN, value):
1390
1394
  raise ValidationError(
1391
1395
  code="engine.validation.validate_model_group_configuration_setup"
1392
1396
  ".sensitive_key_string_value_must_be_set_as_env_var",
@@ -8,6 +8,7 @@ import httpx
8
8
 
9
9
  from rasa.shared.agents.auth.auth_strategy import AgentAuthStrategy
10
10
  from rasa.shared.agents.auth.constants import (
11
+ CONFIG_AUDIENCE_KEY,
11
12
  CONFIG_CLIENT_ID_KEY,
12
13
  CONFIG_CLIENT_SECRET_KEY,
13
14
  CONFIG_OAUTH_KEY,
@@ -35,12 +36,14 @@ class OAuth2AuthStrategy(AgentAuthStrategy):
35
36
  token_url: str,
36
37
  client_id: str,
37
38
  client_secret: str,
38
- scope: str,
39
+ audience: Optional[str] = None,
40
+ scope: Optional[str] = None,
39
41
  timeout: Optional[int] = None,
40
42
  ):
41
43
  self.token_url = token_url
42
44
  self.client_id = client_id
43
45
  self.client_secret = client_secret
46
+ self.audience = audience
44
47
  self.scope = scope
45
48
  self.timeout = timeout or self.DEFAULT_ACCESS_TOKEN_TIMEOUT
46
49
 
@@ -65,6 +68,7 @@ class OAuth2AuthStrategy(AgentAuthStrategy):
65
68
  token_url = oauth_config.get(CONFIG_TOKEN_URL_KEY)
66
69
  client_id = oauth_config.get(CONFIG_CLIENT_ID_KEY)
67
70
  client_secret = oauth_config.get(CONFIG_CLIENT_SECRET_KEY)
71
+ audience = oauth_config.get(CONFIG_AUDIENCE_KEY)
68
72
  scope = oauth_config.get(CONFIG_SCOPE_KEY)
69
73
  timeout = (
70
74
  oauth_config.get(CONFIG_TIMEOUT_KEY) or cls.DEFAULT_ACCESS_TOKEN_TIMEOUT
@@ -76,13 +80,12 @@ class OAuth2AuthStrategy(AgentAuthStrategy):
76
80
  raise ValueError("Client ID is required for OAuth2 authentication")
77
81
  if not client_secret:
78
82
  raise ValueError("Client secret is required for OAuth2 authentication")
79
- if not scope:
80
- raise ValueError("Scope is required for OAuth2 authentication")
81
83
 
82
84
  return cls(
83
85
  token_url=token_url,
84
86
  client_id=client_id,
85
87
  client_secret=client_secret,
88
+ audience=audience,
86
89
  scope=scope,
87
90
  timeout=timeout,
88
91
  )
@@ -118,8 +121,11 @@ class OAuth2AuthStrategy(AgentAuthStrategy):
118
121
  "grant_type": self._grant_type,
119
122
  "client_id": self.client_id,
120
123
  "client_secret": self.client_secret,
121
- "scope": self.scope,
122
124
  }
125
+ if self.scope:
126
+ data["scope"] = self.scope
127
+ if self.audience:
128
+ data["audience"] = self.audience
123
129
 
124
130
  # Resolve environment variables in data.
125
131
  resolved_data = resolve_environment_variables(data)
@@ -7,6 +7,7 @@ CONFIG_CLIENT_ID_KEY = "client_id"
7
7
  CONFIG_CLIENT_SECRET_KEY = "client_secret"
8
8
  CONFIG_TOKEN_URL_KEY = "token_url"
9
9
  CONFIG_SCOPE_KEY = "scope"
10
+ CONFIG_AUDIENCE_KEY = "audience"
10
11
  CONFIG_GRANT_TYPE_KEY = "grant_type"
11
12
  CONFIG_TIMEOUT_KEY = "timeout"
12
13
  CONFIG_MODULE_KEY = "module"
@@ -0,0 +1,85 @@
1
+ """Utilities for agent authentication and secret validation."""
2
+
3
+ import re
4
+ from typing import Any, Dict
5
+
6
+ from rasa.exceptions import ValidationError
7
+ from rasa.shared.agents.auth.constants import (
8
+ CONFIG_API_KEY_KEY,
9
+ CONFIG_CLIENT_SECRET_KEY,
10
+ CONFIG_OAUTH_KEY,
11
+ CONFIG_TOKEN_KEY,
12
+ )
13
+ from rasa.shared.constants import SECRET_DATA_FORMAT_PATTERN
14
+
15
+ AUTH_SECRETS = [CONFIG_API_KEY_KEY, CONFIG_TOKEN_KEY, CONFIG_CLIENT_SECRET_KEY]
16
+
17
+
18
+ def _is_valid_secret_data_format(value: str) -> bool:
19
+ """Check if a value is in the correct environment variable format.
20
+
21
+ Args:
22
+ value: The value to check
23
+
24
+ Returns:
25
+ True if the value is in format "${env_var}", False otherwise
26
+ """
27
+ if not isinstance(value, str):
28
+ return False
29
+
30
+ # Use the common regex pattern for environment variable validation
31
+ return bool(re.match(SECRET_DATA_FORMAT_PATTERN, value))
32
+
33
+
34
+ def _validate_secret_value(value: Any, key: str, context: str) -> None:
35
+ """Generic function to validate a single secret value.
36
+
37
+ Args:
38
+ value: The value to validate
39
+ key: The key name for error messages
40
+ context: Context for error messages
41
+ (e.g., "agent 'my_agent'", "MCP server 'my_server'")
42
+ """
43
+ if isinstance(value, str):
44
+ if not _is_valid_secret_data_format(value):
45
+ raise ValidationError(
46
+ code="validation.sensitive_key_string_value_must_be_set_as_env_var",
47
+ event_info=(
48
+ f"You defined the '{key}' in {context} as a string. The '{key}' "
49
+ f"must be set as an environment variable. Please update your "
50
+ f"config."
51
+ ),
52
+ key=key,
53
+ )
54
+ else:
55
+ raise ValidationError(
56
+ code="validation.sensitive_key_must_be_set_as_env_var",
57
+ event_info=(
58
+ f"You should define the '{key}' in {context} using the environment "
59
+ f"variable syntax - ${{ENV_VARIABLE_NAME}}. Please update your config."
60
+ ),
61
+ key=key,
62
+ )
63
+
64
+
65
+ def validate_secrets_in_params(
66
+ params: Dict[str, Any], context_name: str = "configuration"
67
+ ) -> None:
68
+ """Validate that secrets in params are in environment variable format.
69
+
70
+ Args:
71
+ params: The parameters dictionary to validate
72
+ context_name: Name of the context for error messages
73
+ (e.g., "agent", "MCP server")
74
+ """
75
+ for key, value in params.items():
76
+ if key in AUTH_SECRETS:
77
+ _validate_secret_value(value, key, context_name)
78
+ elif key == CONFIG_OAUTH_KEY and isinstance(value, dict):
79
+ # Handle oauth object specifically - we know it contains client_secret
80
+ if CONFIG_CLIENT_SECRET_KEY in value:
81
+ _validate_secret_value(
82
+ value[CONFIG_CLIENT_SECRET_KEY],
83
+ CONFIG_CLIENT_SECRET_KEY,
84
+ context_name,
85
+ )
rasa/shared/constants.py CHANGED
@@ -367,3 +367,6 @@ FAQ_DOCUMENT_LINE_SEPARATOR = "\n"
367
367
 
368
368
  # Constants for the MCP server
369
369
  KEY_TOOL_CALLS = "tool_calls"
370
+
371
+ # Regex pattern for secrets data validation
372
+ SECRET_DATA_FORMAT_PATTERN = r"\${(\w+)}"
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Text
5
5
 
6
- from rasa.core.available_agents import AvailableAgents
6
+ from rasa.core.config.configuration import Configuration
7
7
  from rasa.shared.core.flows.flow_step import FlowStep
8
8
 
9
9
  if TYPE_CHECKING:
@@ -102,7 +102,7 @@ class CallFlowStep(FlowStep):
102
102
 
103
103
  def is_calling_agent(self) -> bool:
104
104
  """Returns True if the call references an agent."""
105
- return self.call in AvailableAgents.get_instance().agents
105
+ return self.call in Configuration.get_instance().available_agents.agents
106
106
 
107
107
  @property
108
108
  def default_id_postfix(self) -> str:
rasa/shared/utils/llm.py CHANGED
@@ -23,6 +23,7 @@ from typing import (
23
23
  )
24
24
 
25
25
  import structlog
26
+ from jinja2 import Environment, select_autoescape
26
27
  from pydantic import BaseModel, Field
27
28
 
28
29
  import rasa.cli.telemetry
@@ -718,6 +719,38 @@ def embedder_client_factory(
718
719
  return client
719
720
 
720
721
 
722
+ def validate_jinja2_template(template_content: Text) -> None:
723
+ """Validate that a template string has valid Jinja2 syntax.
724
+
725
+ Args:
726
+ template_content: The template content to validate
727
+
728
+ Raises:
729
+ jinja2.exceptions.TemplateSyntaxError: If the template has invalid syntax
730
+ Exception: If there's an error during validation
731
+ """
732
+ # Create environment with custom filters
733
+ env = Environment(
734
+ autoescape=select_autoescape(
735
+ disabled_extensions=["jinja2"],
736
+ default_for_string=False,
737
+ default=True,
738
+ )
739
+ )
740
+ # Register filters - lazy import to avoid circular dependencies
741
+ from rasa.dialogue_understanding.generator._jinja_filters import (
742
+ to_json_escaped_string,
743
+ )
744
+ from rasa.dialogue_understanding.generator.constants import (
745
+ TO_JSON_ESCAPED_STRING_JINJA_FILTER,
746
+ )
747
+
748
+ env.filters[TO_JSON_ESCAPED_STRING_JINJA_FILTER] = to_json_escaped_string
749
+
750
+ # Validate Jinja2 syntax
751
+ env.from_string(template_content)
752
+
753
+
721
754
  def get_prompt_template(
722
755
  jinja_file_path: Optional[Text],
723
756
  default_prompt_template: Text,
rasa/telemetry.py CHANGED
@@ -1129,11 +1129,11 @@ def track_model_training(
1129
1129
  def _collect_agent_configuration(flows: FlowsList) -> Dict[str, Any]:
1130
1130
  agent_data: Dict[str, Any] = {}
1131
1131
 
1132
- from rasa.core.available_agents import AvailableAgents
1133
1132
  from rasa.core.config.configuration import Configuration
1134
1133
 
1135
- agents = AvailableAgents.get_instance().agents
1136
- mcp_servers = Configuration.get_instance().endpoints.mcp_servers
1134
+ configuration = Configuration.get_instance()
1135
+ agents = configuration.available_agents.agents
1136
+ mcp_servers = configuration.endpoints.mcp_servers
1137
1137
 
1138
1138
  if not agents and not mcp_servers:
1139
1139
  return agent_data
rasa/utils/pypred.py CHANGED
@@ -10,6 +10,7 @@ https://rasahq.atlassian.net/browse/ATO-1925
10
10
  The solution is based on https://github.com/FreeCAD/FreeCAD/issues/6315
11
11
  """
12
12
 
13
+ import logging
13
14
  from typing import Any
14
15
 
15
16
  import ply.yacc
@@ -19,12 +20,18 @@ from pypred import Predicate as OriginalPredicate # noqa: TID251
19
20
  # Store the original yacc function
20
21
  _original_yacc = ply.yacc.yacc
21
22
 
23
+ # Create a logger that suppresses warnings to avoid yacc table file version warnings
24
+ _yacc_logger = logging.getLogger("ply.yacc")
25
+ _yacc_logger.setLevel(logging.ERROR)
26
+
22
27
 
23
28
  def patched_yacc(*args: Any, **kwargs: Any) -> Any:
24
29
  # Disable generation of debug ('parser.out') and table
25
30
  # cache ('parsetab.py'), as it requires a writable location.
26
31
  kwargs["write_tables"] = False
27
32
  kwargs["module"] = pypred.parser
33
+ # Suppress yacc warnings by using a logger that only shows errors
34
+ kwargs["errorlog"] = _yacc_logger
28
35
  return _original_yacc(*args, **kwargs)
29
36
 
30
37