rasa-pro 3.14.0.dev20250922__py3-none-any.whl → 3.14.0rc2__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.
- rasa/__main__.py +15 -3
- rasa/agents/__init__.py +0 -0
- rasa/agents/agent_factory.py +122 -0
- rasa/agents/agent_manager.py +211 -0
- rasa/agents/constants.py +43 -0
- rasa/agents/core/__init__.py +0 -0
- rasa/agents/core/agent_protocol.py +107 -0
- rasa/agents/core/types.py +81 -0
- rasa/agents/exceptions.py +38 -0
- rasa/agents/protocol/__init__.py +5 -0
- rasa/agents/protocol/a2a/__init__.py +0 -0
- rasa/agents/protocol/a2a/a2a_agent.py +879 -0
- rasa/agents/protocol/mcp/__init__.py +0 -0
- rasa/agents/protocol/mcp/mcp_base_agent.py +726 -0
- rasa/agents/protocol/mcp/mcp_open_agent.py +327 -0
- rasa/agents/protocol/mcp/mcp_task_agent.py +522 -0
- rasa/agents/schemas/__init__.py +13 -0
- rasa/agents/schemas/agent_input.py +38 -0
- rasa/agents/schemas/agent_output.py +26 -0
- rasa/agents/schemas/agent_tool_result.py +65 -0
- rasa/agents/schemas/agent_tool_schema.py +186 -0
- rasa/agents/templates/__init__.py +0 -0
- rasa/agents/templates/mcp_open_agent_prompt_template.jinja2 +20 -0
- rasa/agents/templates/mcp_task_agent_prompt_template.jinja2 +22 -0
- rasa/agents/utils.py +206 -0
- rasa/agents/validation.py +485 -0
- rasa/api.py +24 -9
- rasa/builder/config.py +6 -2
- rasa/builder/copilot/constants.py +4 -1
- rasa/builder/copilot/copilot.py +155 -79
- rasa/builder/copilot/models.py +304 -108
- rasa/builder/copilot/prompts/copilot_training_error_handler_prompt.jinja2 +53 -0
- rasa/builder/guardrails/{lakera.py → clients.py} +55 -5
- rasa/builder/guardrails/constants.py +3 -0
- rasa/builder/guardrails/models.py +45 -10
- rasa/builder/guardrails/policy_checker.py +324 -0
- rasa/builder/guardrails/utils.py +42 -276
- rasa/builder/jobs.py +182 -12
- rasa/builder/llm_service.py +32 -5
- rasa/builder/models.py +13 -3
- rasa/builder/project_generator.py +6 -1
- rasa/builder/service.py +31 -15
- rasa/builder/training_service.py +18 -24
- rasa/builder/validation_service.py +1 -1
- rasa/cli/arguments/default_arguments.py +12 -0
- rasa/cli/arguments/run.py +2 -0
- rasa/cli/arguments/train.py +2 -0
- rasa/cli/data.py +10 -8
- rasa/cli/dialogue_understanding_test.py +10 -7
- rasa/cli/e2e_test.py +9 -6
- rasa/cli/evaluate.py +4 -2
- rasa/cli/export.py +5 -2
- rasa/cli/inspect.py +8 -4
- rasa/cli/interactive.py +5 -4
- rasa/cli/llm_fine_tuning.py +11 -6
- rasa/cli/project_templates/finance/domain/general/help.yml +0 -0
- rasa/cli/project_templates/tutorial/credentials.yml +10 -0
- rasa/cli/run.py +12 -10
- rasa/cli/scaffold.py +4 -4
- rasa/cli/shell.py +9 -5
- rasa/cli/studio/studio.py +1 -1
- rasa/cli/test.py +34 -14
- rasa/cli/train.py +41 -28
- rasa/cli/utils.py +1 -393
- rasa/cli/validation/__init__.py +0 -0
- rasa/cli/validation/bot_config.py +223 -0
- rasa/cli/validation/config_path_validation.py +257 -0
- rasa/cli/x.py +8 -4
- rasa/constants.py +7 -1
- rasa/core/actions/action.py +51 -10
- rasa/core/actions/grpc_custom_action_executor.py +1 -1
- rasa/core/agent.py +19 -2
- rasa/core/available_agents.py +229 -0
- rasa/core/brokers/kafka.py +5 -1
- rasa/core/channels/__init__.py +82 -35
- rasa/core/channels/development_inspector.py +3 -3
- rasa/core/channels/inspector/README.md +25 -13
- rasa/core/channels/inspector/dist/assets/{arc-35222594.js → arc-6177260a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-a0efbfd3.js → blockDiagram-38ab4fdb-b054f038.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-0584c0f2.js → c4Diagram-3d4e48cf-f25427d5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-bf9cbb34.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-39f40dbe.js → classDiagram-70f12bd4-c7a2af53.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-1ad755f3.js → classDiagram-v2-f2320105-58db65c0.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-8f9083bb.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-b0f4f0fe.js → createText-2e5e7dd3-088372e2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-9039bff9.js → edges-e0da2a9e-58676240.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-65c9b127.js → erDiagram-9861fffd-0c14d7c6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-4f08b38e.js → flowDb-956e92f1-ea63f85c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-e95c362a.js → flowDiagram-66a62f08-a2af48cd.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-9ecd5b59.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-703c3015.js → flowchart-elk-definition-4a651766-6937abe7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-699328ea.js → ganttDiagram-c361ad54-7473f357.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-04cf4b05.js → gitGraphDiagram-72cf32ee-d0c9405e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-ee94449e.js → graph-0a6f8466.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-940162b4.js → index-3862675e-7610671a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/index-74e01d94.js +1354 -0
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-c79c2866.js → infoDiagram-f8f76790-be397dc7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-84489d30.js → journeyDiagram-49397b02-4cefbf62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-a9aa9858.js → layout-e7fbc2bf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-eb73cf26.js → line-a8aa457c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-b3399f9a.js → linear-3351e0d2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-b095bf1a.js → mindmap-definition-fc14e90a-b8cbf605.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-07644b66.js → pieDiagram-8a3498a8-f327f774.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-573a3f9c.js → quadrantDiagram-120e2f19-2854c591.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-d457e1e1.js → requirementDiagram-deff3bca-964985d5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-9d26e1a2.js → sankeyDiagram-04a897e0-edeb4f33.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3a9cde10.js → sequenceDiagram-704730f1-fcf70125.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-4f3e8cec.js → stateDiagram-587899a1-0e770395.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e617e5bf.js → stateDiagram-v2-d93cdb3a-af8dcd22.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-eab30d2f.js → styles-6aaf32cf-36a9e70d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-09994be2.js → styles-9a916d00-884a8b5b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-b7110364.js → styles-c10674c1-dc097813.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-3ebc92ad.js → svgDrawCommon-08f97a94-5a2c7eed.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-7d13d2f2.js → timeline-definition-85554ec2-e89c4f6e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-488385e1.js → xychartDiagram-e933f94c-afb6fe56.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/package.json +18 -18
- rasa/core/channels/inspector/src/App.tsx +29 -4
- rasa/core/channels/inspector/src/components/DialogueAgentStack.tsx +108 -0
- rasa/core/channels/inspector/src/components/{DialogueStack.tsx → DialogueHistoryStack.tsx} +4 -2
- rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +7 -4
- rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -0
- rasa/core/channels/inspector/src/helpers/formatters.ts +24 -3
- rasa/core/channels/inspector/src/helpers/utils.test.ts +127 -0
- rasa/core/channels/inspector/src/helpers/utils.ts +66 -1
- rasa/core/channels/inspector/src/theme/base/styles.ts +19 -1
- rasa/core/channels/inspector/src/types.ts +21 -0
- rasa/core/channels/inspector/yarn.lock +336 -189
- rasa/core/channels/studio_chat.py +6 -6
- rasa/core/channels/telegram.py +4 -9
- rasa/core/channels/voice_stream/genesys.py +1 -1
- rasa/core/channels/voice_stream/tts/deepgram.py +140 -0
- rasa/core/channels/voice_stream/twilio_media_streams.py +5 -1
- rasa/core/channels/voice_stream/voice_channel.py +3 -0
- rasa/core/concurrent_lock_store.py +38 -21
- rasa/core/config/__init__.py +0 -0
- rasa/core/{available_endpoints.py → config/available_endpoints.py} +51 -16
- rasa/core/config/configuration.py +260 -0
- rasa/core/config/credentials.py +19 -0
- rasa/core/config/message_procesing_config.py +34 -0
- rasa/core/constants.py +10 -0
- rasa/core/iam_credentials_providers/aws_iam_credentials_providers.py +69 -4
- rasa/core/iam_credentials_providers/credentials_provider_protocol.py +2 -1
- rasa/core/lock_store.py +4 -0
- rasa/core/policies/enterprise_search_policy.py +5 -3
- rasa/core/policies/flow_policy.py +4 -4
- rasa/core/policies/flows/agent_executor.py +632 -0
- rasa/core/policies/flows/flow_executor.py +136 -75
- rasa/core/policies/flows/mcp_tool_executor.py +298 -0
- rasa/core/policies/intentless_policy.py +1 -1
- rasa/core/policies/ted_policy.py +20 -12
- rasa/core/policies/unexpected_intent_policy.py +6 -0
- rasa/core/processor.py +68 -44
- rasa/core/redis_connection_factory.py +7 -2
- rasa/core/run.py +37 -8
- rasa/core/test.py +4 -0
- rasa/core/tracker_stores/redis_tracker_store.py +4 -0
- rasa/core/tracker_stores/sql_tracker_store.py +3 -1
- rasa/core/tracker_stores/tracker_store.py +3 -7
- rasa/core/train.py +1 -1
- rasa/core/training/interactive.py +20 -18
- rasa/core/training/story_conflict.py +5 -5
- rasa/core/utils.py +22 -23
- rasa/dialogue_understanding/commands/__init__.py +8 -0
- rasa/dialogue_understanding/commands/cancel_flow_command.py +19 -5
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +21 -2
- rasa/dialogue_understanding/commands/clarify_command.py +20 -2
- rasa/dialogue_understanding/commands/continue_agent_command.py +91 -0
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +21 -2
- rasa/dialogue_understanding/commands/restart_agent_command.py +162 -0
- rasa/dialogue_understanding/commands/start_flow_command.py +68 -7
- rasa/dialogue_understanding/commands/utils.py +124 -2
- rasa/dialogue_understanding/generator/command_parser.py +4 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +50 -12
- rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +1 -1
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +66 -0
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +66 -0
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +89 -0
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +88 -0
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +42 -7
- rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +40 -3
- rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +20 -3
- rasa/dialogue_understanding/patterns/cancel.py +27 -6
- rasa/dialogue_understanding/patterns/clarify.py +3 -14
- rasa/dialogue_understanding/patterns/continue_interrupted.py +239 -6
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +46 -8
- rasa/dialogue_understanding/processor/command_processor.py +136 -15
- rasa/dialogue_understanding/stack/dialogue_stack.py +98 -2
- rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +57 -0
- rasa/dialogue_understanding/stack/utils.py +57 -3
- rasa/dialogue_understanding/utils.py +24 -4
- rasa/dialogue_understanding_test/du_test_runner.py +8 -3
- rasa/e2e_test/e2e_test_runner.py +13 -3
- rasa/engine/caching.py +2 -2
- rasa/engine/constants.py +1 -1
- rasa/engine/recipes/default_components.py +138 -49
- rasa/engine/recipes/default_recipe.py +108 -11
- rasa/engine/runner/dask.py +8 -5
- rasa/engine/validation.py +19 -6
- rasa/graph_components/validators/default_recipe_validator.py +86 -28
- rasa/hooks.py +5 -5
- rasa/llm_fine_tuning/utils.py +2 -2
- rasa/model_training.py +60 -47
- rasa/nlu/classifiers/diet_classifier.py +198 -98
- rasa/nlu/classifiers/logistic_regression_classifier.py +1 -4
- rasa/nlu/classifiers/mitie_intent_classifier.py +3 -0
- rasa/nlu/classifiers/sklearn_intent_classifier.py +1 -3
- rasa/nlu/extractors/crf_entity_extractor.py +9 -10
- rasa/nlu/extractors/mitie_entity_extractor.py +3 -0
- rasa/nlu/extractors/spacy_entity_extractor.py +3 -0
- rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +4 -0
- rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +5 -0
- rasa/nlu/featurizers/dense_featurizer/mitie_featurizer.py +2 -0
- rasa/nlu/featurizers/dense_featurizer/spacy_featurizer.py +3 -0
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +4 -2
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +4 -0
- rasa/nlu/selectors/response_selector.py +10 -2
- rasa/nlu/tokenizers/jieba_tokenizer.py +3 -4
- rasa/nlu/tokenizers/mitie_tokenizer.py +3 -2
- rasa/nlu/tokenizers/spacy_tokenizer.py +3 -2
- rasa/nlu/utils/mitie_utils.py +3 -0
- rasa/nlu/utils/spacy_utils.py +3 -2
- rasa/plugin.py +8 -8
- rasa/privacy/privacy_manager.py +12 -3
- rasa/server.py +15 -3
- rasa/shared/agents/__init__.py +0 -0
- rasa/shared/agents/auth/__init__.py +0 -0
- rasa/shared/agents/auth/agent_auth_factory.py +105 -0
- rasa/shared/agents/auth/agent_auth_manager.py +92 -0
- rasa/shared/agents/auth/auth_strategy/__init__.py +19 -0
- rasa/shared/agents/auth/auth_strategy/agent_auth_strategy.py +52 -0
- rasa/shared/agents/auth/auth_strategy/api_key_auth_strategy.py +42 -0
- rasa/shared/agents/auth/auth_strategy/bearer_token_auth_strategy.py +28 -0
- rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +167 -0
- rasa/shared/agents/auth/constants.py +12 -0
- rasa/shared/agents/auth/types.py +12 -0
- rasa/shared/agents/utils.py +35 -0
- rasa/shared/constants.py +8 -0
- rasa/shared/core/constants.py +16 -1
- rasa/shared/core/domain.py +0 -7
- rasa/shared/core/events.py +327 -0
- rasa/shared/core/flows/constants.py +5 -0
- rasa/shared/core/flows/flows_list.py +21 -5
- rasa/shared/core/flows/flows_yaml_schema.json +119 -184
- rasa/shared/core/flows/steps/call.py +49 -5
- rasa/shared/core/flows/steps/collect.py +98 -13
- rasa/shared/core/flows/validation.py +372 -8
- rasa/shared/core/flows/yaml_flows_io.py +3 -2
- rasa/shared/core/slots.py +2 -2
- rasa/shared/core/trackers.py +5 -2
- rasa/shared/exceptions.py +16 -0
- rasa/shared/importers/rasa.py +1 -1
- rasa/shared/importers/utils.py +9 -3
- rasa/shared/providers/llm/_base_litellm_client.py +41 -9
- rasa/shared/providers/llm/litellm_router_llm_client.py +8 -4
- rasa/shared/providers/llm/llm_client.py +7 -3
- rasa/shared/providers/llm/llm_response.py +66 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +8 -4
- rasa/shared/utils/common.py +24 -0
- rasa/shared/utils/health_check/health_check.py +7 -3
- rasa/shared/utils/llm.py +39 -16
- rasa/shared/utils/mcp/__init__.py +0 -0
- rasa/shared/utils/mcp/server_connection.py +247 -0
- rasa/shared/utils/mcp/utils.py +20 -0
- rasa/shared/utils/schemas/events.py +42 -0
- rasa/shared/utils/yaml.py +3 -1
- rasa/studio/pull/pull.py +3 -2
- rasa/studio/train.py +8 -7
- rasa/studio/upload.py +3 -6
- rasa/telemetry.py +69 -5
- rasa/tracing/config.py +45 -12
- rasa/tracing/constants.py +14 -0
- rasa/tracing/instrumentation/attribute_extractors.py +142 -9
- rasa/tracing/instrumentation/instrumentation.py +626 -21
- rasa/tracing/instrumentation/intentless_policy_instrumentation.py +4 -4
- rasa/tracing/instrumentation/metrics.py +32 -0
- rasa/tracing/metric_instrument_provider.py +68 -0
- rasa/utils/common.py +92 -1
- rasa/utils/endpoints.py +11 -2
- rasa/utils/log_utils.py +96 -5
- rasa/utils/ml_utils.py +1 -1
- rasa/utils/tensorflow/__init__.py +7 -0
- rasa/utils/tensorflow/callback.py +136 -101
- rasa/utils/tensorflow/crf.py +1 -1
- rasa/utils/tensorflow/data_generator.py +21 -8
- rasa/utils/tensorflow/layers.py +21 -11
- rasa/utils/tensorflow/metrics.py +7 -3
- rasa/utils/tensorflow/models.py +56 -8
- rasa/utils/tensorflow/rasa_layers.py +8 -6
- rasa/utils/tensorflow/transformer.py +2 -3
- rasa/utils/train_utils.py +54 -24
- rasa/validator.py +5 -5
- rasa/version.py +1 -1
- {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc2.dist-info}/METADATA +47 -41
- {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc2.dist-info}/RECORD +299 -238
- rasa/builder/scrape_rasa_docs.py +0 -97
- rasa/core/channels/inspector/dist/assets/channel-8e08bed9.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-78c82dea.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2b08f601.js +0 -1
- rasa/core/channels/inspector/dist/assets/index-c941dcb3.js +0 -1336
- {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc2.dist-info}/WHEEL +0 -0
- {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc2.dist-info}/entry_points.txt +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import Any, Dict, List, Optional, Set, Text, Union
|
|
5
6
|
|
|
6
7
|
import structlog
|
|
7
8
|
|
|
8
9
|
from rasa.shared.constants import ACTION_ASK_PREFIX, UTTER_ASK_PREFIX
|
|
10
|
+
from rasa.shared.core.constants import GLOBAL_SILENCE_TIMEOUT_DEFAULT_VALUE
|
|
9
11
|
from rasa.shared.core.flows.flow_step import FlowStep
|
|
10
12
|
from rasa.shared.core.slots import SlotRejection
|
|
11
13
|
from rasa.shared.exceptions import RasaException
|
|
@@ -16,7 +18,93 @@ DEFAULT_FORCE_SLOT_FILLING = False
|
|
|
16
18
|
|
|
17
19
|
logger = structlog.get_logger(__name__)
|
|
18
20
|
|
|
19
|
-
SilenceTimeoutInstructionType = Union[int, float, Dict[str,
|
|
21
|
+
SilenceTimeoutInstructionType = Union[int, float, Dict[str, float]]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SilenceTimeout(ABC):
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def get_silence_for_channel(self, channel_name: str) -> float:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def to_json(self) -> Dict[str, Any]:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SingleSilenceTimeout(SilenceTimeout):
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
silence_timeout: float,
|
|
38
|
+
):
|
|
39
|
+
SingleSilenceTimeout._validate(silence_timeout)
|
|
40
|
+
self.silence_timeout = silence_timeout
|
|
41
|
+
|
|
42
|
+
def get_silence_for_channel(self, channel_name: str) -> float:
|
|
43
|
+
return self.silence_timeout
|
|
44
|
+
|
|
45
|
+
def to_json(self) -> Dict[str, Any]:
|
|
46
|
+
return {
|
|
47
|
+
"silence_timeout": self.silence_timeout,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def __eq__(self, other: Any) -> bool:
|
|
51
|
+
if not isinstance(other, SingleSilenceTimeout):
|
|
52
|
+
return False
|
|
53
|
+
return self.silence_timeout == other.silence_timeout
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _validate(silence_timeout: float) -> None:
|
|
57
|
+
if silence_timeout and silence_timeout < 0:
|
|
58
|
+
raise RasaException(
|
|
59
|
+
f"Invalid silence timeout value: {silence_timeout}. "
|
|
60
|
+
"Silence timeout must be a non-negative number."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_json(cls, silence_timeout: float) -> SingleSilenceTimeout:
|
|
65
|
+
SingleSilenceTimeout._validate(silence_timeout)
|
|
66
|
+
return cls(silence_timeout)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class PerChannelSilenceTimeout(SilenceTimeout):
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
channel_silence_timeouts: Dict[str, float],
|
|
73
|
+
):
|
|
74
|
+
self.silence_timeouts = channel_silence_timeouts
|
|
75
|
+
|
|
76
|
+
def get_silence_for_channel(self, channel_name: str) -> float:
|
|
77
|
+
return self.silence_timeouts.get(
|
|
78
|
+
channel_name, GLOBAL_SILENCE_TIMEOUT_DEFAULT_VALUE
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def to_json(self) -> Dict[str, Any]:
|
|
82
|
+
return {
|
|
83
|
+
"silence_timeout": self.silence_timeouts,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def __eq__(self, other: Any) -> bool:
|
|
87
|
+
if not isinstance(other, PerChannelSilenceTimeout):
|
|
88
|
+
return False
|
|
89
|
+
return self.silence_timeouts == other.silence_timeouts
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _validate(silence_timeout_json: Dict[str, Any]) -> None:
|
|
93
|
+
for channel, timeout in silence_timeout_json.items():
|
|
94
|
+
if not isinstance(timeout, (int, float)) or timeout < 0:
|
|
95
|
+
raise RasaException(
|
|
96
|
+
f"Invalid silence timeout value: {timeout} for "
|
|
97
|
+
f"channel '{channel}'. "
|
|
98
|
+
"If defined at collect step, silence timeout "
|
|
99
|
+
"must be a non-negative number."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def from_json(
|
|
104
|
+
cls, channel_silence_timeouts: Dict[str, Any]
|
|
105
|
+
) -> PerChannelSilenceTimeout:
|
|
106
|
+
PerChannelSilenceTimeout._validate(channel_silence_timeouts)
|
|
107
|
+
return cls(channel_silence_timeouts)
|
|
20
108
|
|
|
21
109
|
|
|
22
110
|
@dataclass
|
|
@@ -37,7 +125,7 @@ class CollectInformationFlowStep(FlowStep):
|
|
|
37
125
|
"""Whether to reset the slot value at the end of the flow."""
|
|
38
126
|
force_slot_filling: bool = False
|
|
39
127
|
"""Whether to keep only the SetSlot command for the collected slot."""
|
|
40
|
-
silence_timeout: Optional[
|
|
128
|
+
silence_timeout: Optional[SilenceTimeout] = None
|
|
41
129
|
"""The silence timeout for the collect information step."""
|
|
42
130
|
|
|
43
131
|
@classmethod
|
|
@@ -79,25 +167,22 @@ class CollectInformationFlowStep(FlowStep):
|
|
|
79
167
|
@staticmethod
|
|
80
168
|
def _deserialise_silence_timeout(
|
|
81
169
|
silence_timeout_json: Optional[SilenceTimeoutInstructionType],
|
|
82
|
-
) -> Optional[
|
|
170
|
+
) -> Optional[SilenceTimeout]:
|
|
83
171
|
"""Deserialize silence timeout from JSON."""
|
|
84
172
|
if not silence_timeout_json:
|
|
85
173
|
return None
|
|
86
174
|
|
|
87
|
-
if not isinstance(silence_timeout_json, (int, float)):
|
|
175
|
+
if not isinstance(silence_timeout_json, (int, float, dict)):
|
|
88
176
|
raise RasaException(
|
|
89
177
|
f"Invalid silence timeout value: {silence_timeout_json}. "
|
|
90
|
-
"If defined at collect step, silence timeout must be a number
|
|
178
|
+
"If defined at collect step, silence timeout must be a number "
|
|
179
|
+
"or a map between channel names and timeout values."
|
|
91
180
|
)
|
|
92
181
|
|
|
93
|
-
|
|
182
|
+
if isinstance(silence_timeout_json, dict):
|
|
183
|
+
return PerChannelSilenceTimeout.from_json(silence_timeout_json)
|
|
94
184
|
|
|
95
|
-
|
|
96
|
-
raise RasaException(
|
|
97
|
-
f"Invalid silence timeout value: {silence_timeout}. "
|
|
98
|
-
"Silence timeout must be a non-negative number."
|
|
99
|
-
)
|
|
100
|
-
return silence_timeout
|
|
185
|
+
return SingleSilenceTimeout.from_json(silence_timeout_json)
|
|
101
186
|
|
|
102
187
|
@staticmethod
|
|
103
188
|
def _default_utter(collect: str) -> str:
|
|
@@ -119,7 +204,7 @@ class CollectInformationFlowStep(FlowStep):
|
|
|
119
204
|
data["rejections"] = [rejection.as_dict() for rejection in self.rejections]
|
|
120
205
|
data["force_slot_filling"] = self.force_slot_filling
|
|
121
206
|
if self.silence_timeout:
|
|
122
|
-
data
|
|
207
|
+
data.update(self.silence_timeout.to_json())
|
|
123
208
|
|
|
124
209
|
return super().as_json(step_properties=data)
|
|
125
210
|
|
|
@@ -4,7 +4,9 @@ import re
|
|
|
4
4
|
import typing
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from typing import List, Optional, Set, Text
|
|
7
|
+
from typing import Iterator, List, Optional, Set, Text, Tuple
|
|
8
|
+
|
|
9
|
+
from jinja2 import Template
|
|
8
10
|
|
|
9
11
|
from rasa.shared.constants import (
|
|
10
12
|
RASA_DEFAULT_FLOW_PATTERN_PREFIX,
|
|
@@ -12,6 +14,11 @@ from rasa.shared.constants import (
|
|
|
12
14
|
RASA_PATTERN_HUMAN_HANDOFF,
|
|
13
15
|
RASA_PATTERN_INTERNAL_ERROR,
|
|
14
16
|
)
|
|
17
|
+
from rasa.shared.core.flows.constants import (
|
|
18
|
+
KEY_MAPPING_INPUT,
|
|
19
|
+
KEY_MAPPING_OUTPUT,
|
|
20
|
+
KEY_MAPPING_SLOT,
|
|
21
|
+
)
|
|
15
22
|
from rasa.shared.core.flows.flow import Flow
|
|
16
23
|
from rasa.shared.core.flows.flow_step import (
|
|
17
24
|
FlowStep,
|
|
@@ -35,6 +42,7 @@ from rasa.shared.core.flows.utils import (
|
|
|
35
42
|
from rasa.shared.exceptions import RasaException
|
|
36
43
|
|
|
37
44
|
if typing.TYPE_CHECKING:
|
|
45
|
+
from rasa.shared.core.domain import Domain
|
|
38
46
|
from rasa.shared.core.flows.flows_list import FlowsList
|
|
39
47
|
|
|
40
48
|
FLOW_ID_REGEX = r"""^[a-zA-Z0-9_][a-zA-Z0-9_-]*?$"""
|
|
@@ -107,6 +115,47 @@ class DuplicatedStepIdException(RasaException):
|
|
|
107
115
|
)
|
|
108
116
|
|
|
109
117
|
|
|
118
|
+
class InvalidExitIfConditionException(RasaException):
|
|
119
|
+
"""Raised when an exit_if condition is invalid."""
|
|
120
|
+
|
|
121
|
+
def __init__(self, step_id: str, flow_id: str, condition: str, reason: str) -> None:
|
|
122
|
+
"""Initializes the exception."""
|
|
123
|
+
self.step_id = step_id
|
|
124
|
+
self.flow_id = flow_id
|
|
125
|
+
self.condition = condition
|
|
126
|
+
self.reason = reason
|
|
127
|
+
|
|
128
|
+
def __str__(self) -> str:
|
|
129
|
+
"""Return a string representation of the exception."""
|
|
130
|
+
return (
|
|
131
|
+
f"Invalid exit_if condition '{self.condition}' in step '{self.step_id}' "
|
|
132
|
+
f"of flow '{self.flow_id}': {self.reason}. "
|
|
133
|
+
f"Please ensure that exit_if conditions contain at least one slot "
|
|
134
|
+
f"reference and use only defined slots with valid predicates."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class ExitIfExclusivityException(RasaException):
|
|
139
|
+
"""Raised when a call step with exit_if has other properties."""
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self, step_id: str, flow_id: str, conflicting_properties: List[str]
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Initializes the exception."""
|
|
145
|
+
self.step_id = step_id
|
|
146
|
+
self.flow_id = flow_id
|
|
147
|
+
self.conflicting_properties = conflicting_properties
|
|
148
|
+
|
|
149
|
+
def __str__(self) -> str:
|
|
150
|
+
"""Return a string representation of the exception."""
|
|
151
|
+
conflicting_properties_str = ", ".join(self.conflicting_properties)
|
|
152
|
+
return (
|
|
153
|
+
f"Call step '{self.step_id}' in flow '{self.flow_id}' has an 'exit_if' "
|
|
154
|
+
f"property but also has other properties: {conflicting_properties_str}. "
|
|
155
|
+
f"A call step with 'exit_if' cannot have any other properties."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
110
159
|
class DuplicatedFlowIdException(RasaException):
|
|
111
160
|
"""Raised when a flow is using the same id as another flow."""
|
|
112
161
|
|
|
@@ -261,7 +310,7 @@ class NoLinkAllowedInCalledFlowException(RasaException):
|
|
|
261
310
|
)
|
|
262
311
|
|
|
263
312
|
|
|
264
|
-
class
|
|
313
|
+
class UnresolvedLinkFlowException(RasaException):
|
|
265
314
|
"""Raised when a flow is called or linked from another flow but doesn't exist."""
|
|
266
315
|
|
|
267
316
|
def __init__(self, flow_id: str, calling_flow_id: str, step_id: str) -> None:
|
|
@@ -273,13 +322,91 @@ class UnresolvedFlowException(RasaException):
|
|
|
273
322
|
def __str__(self) -> str:
|
|
274
323
|
"""Return a string representation of the exception."""
|
|
275
324
|
return (
|
|
276
|
-
f"Flow '{self.flow_id}' is
|
|
325
|
+
f"Flow '{self.flow_id}' is linked from flow "
|
|
277
326
|
f"'{self.calling_flow_id}' in step '{self.step_id}', "
|
|
278
327
|
f"but it doesn't exist. "
|
|
279
328
|
f"Please make sure that a flow with id '{self.flow_id}' exists."
|
|
280
329
|
)
|
|
281
330
|
|
|
282
331
|
|
|
332
|
+
class UnresolvedCallStepException(RasaException):
|
|
333
|
+
"""Raised when a call step doesn't have a reference to an existing flow or agent."""
|
|
334
|
+
|
|
335
|
+
def __init__(
|
|
336
|
+
self, call_step_argument: str, calling_flow_id: str, step_id: str
|
|
337
|
+
) -> None:
|
|
338
|
+
"""Initializes the exception."""
|
|
339
|
+
self.call_step_argument = call_step_argument
|
|
340
|
+
self.calling_flow_id = calling_flow_id
|
|
341
|
+
self.step_id = step_id
|
|
342
|
+
|
|
343
|
+
def __str__(self) -> str:
|
|
344
|
+
"""Return a string representation of the exception."""
|
|
345
|
+
return (
|
|
346
|
+
f"The call step '{self.step_id}' in flow '{self.calling_flow_id}' "
|
|
347
|
+
f"is invalid: there is no flow or agent with the "
|
|
348
|
+
f"id '{self.call_step_argument}'. "
|
|
349
|
+
f"Please make sure that the call step argument is a valid flow id "
|
|
350
|
+
f"or an agent id."
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class InvalidMCPServerReferenceException(RasaException):
|
|
355
|
+
"""Raised when a call step references a non-existent MCP server."""
|
|
356
|
+
|
|
357
|
+
def __init__(
|
|
358
|
+
self, mcp_server_name: str, calling_flow_id: str, step_id: str
|
|
359
|
+
) -> None:
|
|
360
|
+
"""Initializes the exception."""
|
|
361
|
+
self.mcp_server_name = mcp_server_name
|
|
362
|
+
self.calling_flow_id = calling_flow_id
|
|
363
|
+
self.step_id = step_id
|
|
364
|
+
|
|
365
|
+
def __str__(self) -> str:
|
|
366
|
+
"""Return a string representation of the exception."""
|
|
367
|
+
return (
|
|
368
|
+
f"Call step '{self.step_id}' in flow '{self.calling_flow_id}' "
|
|
369
|
+
f"references MCP server '{self.mcp_server_name}' which does not exist "
|
|
370
|
+
f"in endpoints.yml. Please make sure that the MCP server is properly "
|
|
371
|
+
f"configured in endpoints.yml."
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class InvalidMCPMappingSlotException(RasaException):
|
|
376
|
+
"""Raised when MCP tool mapping references non-existent slots."""
|
|
377
|
+
|
|
378
|
+
def __init__(
|
|
379
|
+
self,
|
|
380
|
+
step_id: str,
|
|
381
|
+
flow_id: str,
|
|
382
|
+
invalid_input_slots: Set[str],
|
|
383
|
+
invalid_output_slots: Set[str],
|
|
384
|
+
) -> None:
|
|
385
|
+
"""Initializes the exception."""
|
|
386
|
+
self.step_id = step_id
|
|
387
|
+
self.flow_id = flow_id
|
|
388
|
+
self.invalid_input_slots = invalid_input_slots
|
|
389
|
+
self.invalid_output_slots = invalid_output_slots
|
|
390
|
+
|
|
391
|
+
def __str__(self) -> str:
|
|
392
|
+
"""Return a string representation of the exception."""
|
|
393
|
+
error_parts = []
|
|
394
|
+
|
|
395
|
+
if self.invalid_input_slots:
|
|
396
|
+
input_slots_str = ", ".join(sorted(self.invalid_input_slots))
|
|
397
|
+
error_parts.append(f"input slots: {input_slots_str}")
|
|
398
|
+
|
|
399
|
+
if self.invalid_output_slots:
|
|
400
|
+
output_slots_str = ", ".join(sorted(self.invalid_output_slots))
|
|
401
|
+
error_parts.append(f"output slots: {output_slots_str}")
|
|
402
|
+
|
|
403
|
+
slots_info = " ".join(error_parts)
|
|
404
|
+
return (
|
|
405
|
+
f"Call step '{self.step_id}' in flow '{self.flow_id}' has slots not "
|
|
406
|
+
f"defined in the domain: {slots_info}"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
283
410
|
class UnresolvedFlowStepIdException(RasaException):
|
|
284
411
|
"""Raised when a flow step is referenced, but its id can not be resolved."""
|
|
285
412
|
|
|
@@ -562,15 +689,201 @@ def validate_link_in_call_restriction(flows: "FlowsList") -> None:
|
|
|
562
689
|
raise NoLinkAllowedInCalledFlowException(step.id, flow.id, step.call)
|
|
563
690
|
|
|
564
691
|
|
|
565
|
-
def
|
|
566
|
-
"""Validates that all called flows exist."""
|
|
692
|
+
def validate_call_steps(flows: "FlowsList") -> None:
|
|
693
|
+
"""Validates that all called flows/agents exist and properties are valid."""
|
|
567
694
|
for flow in flows.underlying_flows:
|
|
568
695
|
for step in flow.steps:
|
|
569
696
|
if not isinstance(step, CallFlowStep):
|
|
570
697
|
continue
|
|
571
698
|
|
|
572
|
-
|
|
573
|
-
|
|
699
|
+
_is_step_calling_agent = step.is_calling_agent()
|
|
700
|
+
_is_step_calling_mcp_tool = step.has_mcp_tool_properties()
|
|
701
|
+
|
|
702
|
+
if (
|
|
703
|
+
not _is_step_calling_agent
|
|
704
|
+
and flows.flow_by_id(step.call) is None
|
|
705
|
+
and not _is_step_calling_mcp_tool
|
|
706
|
+
):
|
|
707
|
+
raise UnresolvedCallStepException(step.call, flow.id, step.id)
|
|
708
|
+
|
|
709
|
+
if step.exit_if and not _is_step_calling_agent:
|
|
710
|
+
# exit_if is only allowed for call steps that call an agent
|
|
711
|
+
raise RasaException(
|
|
712
|
+
f"Call step '{step.id}' in flow '{flow.id}' has an 'exit_if' "
|
|
713
|
+
f"condition, but it is not calling an agent. "
|
|
714
|
+
f"'exit_if' is only allowed for call steps that call an agent."
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def validate_mcp_server_references(flows: "FlowsList") -> None:
|
|
719
|
+
"""Validates that MCP server references in call steps are valid."""
|
|
720
|
+
for flow in flows.underlying_flows:
|
|
721
|
+
for step in flow.steps:
|
|
722
|
+
if not isinstance(step, CallFlowStep):
|
|
723
|
+
continue
|
|
724
|
+
|
|
725
|
+
# Only validate call steps that are trying to call MCP tools
|
|
726
|
+
if step.has_mcp_tool_properties():
|
|
727
|
+
# Check if the referenced MCP server exists
|
|
728
|
+
from rasa.shared.utils.mcp.utils import mcp_server_exists
|
|
729
|
+
|
|
730
|
+
if not mcp_server_exists(step.mcp_server):
|
|
731
|
+
raise InvalidMCPServerReferenceException(
|
|
732
|
+
step.mcp_server, flow.id, step.id
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def validate_mcp_mapping_slots(
|
|
737
|
+
flows: "FlowsList", domain: Optional["Domain"] = None
|
|
738
|
+
) -> None:
|
|
739
|
+
"""Validates that slots referenced in MCP tool mapping are defined in the domain.
|
|
740
|
+
|
|
741
|
+
This function validates that all slots referenced in the input and output
|
|
742
|
+
mapping of MCP tool calls are defined in the domain.
|
|
743
|
+
|
|
744
|
+
Args:
|
|
745
|
+
flows: The flows to validate.
|
|
746
|
+
domain: The domain with slot definitions. If None, slot validation is skipped.
|
|
747
|
+
|
|
748
|
+
Raises:
|
|
749
|
+
InvalidMCPMappingSlotException: If any MCP mapping references undefined slots.
|
|
750
|
+
"""
|
|
751
|
+
if domain is None:
|
|
752
|
+
return
|
|
753
|
+
|
|
754
|
+
domain_slots = {slot.name for slot in domain.slots}
|
|
755
|
+
|
|
756
|
+
for flow in flows.underlying_flows:
|
|
757
|
+
for step in flow.steps:
|
|
758
|
+
if not isinstance(step, CallFlowStep):
|
|
759
|
+
continue
|
|
760
|
+
|
|
761
|
+
# Only validate call steps that are calling MCP tools
|
|
762
|
+
if not step.has_mcp_tool_properties() or step.mapping is None:
|
|
763
|
+
continue
|
|
764
|
+
|
|
765
|
+
# Extract slot names from input mapping
|
|
766
|
+
input_slots = set()
|
|
767
|
+
if KEY_MAPPING_INPUT in step.mapping and isinstance(
|
|
768
|
+
step.mapping[KEY_MAPPING_INPUT], list
|
|
769
|
+
):
|
|
770
|
+
for input_item in step.mapping[KEY_MAPPING_INPUT]:
|
|
771
|
+
input_slots.add(input_item[KEY_MAPPING_SLOT])
|
|
772
|
+
|
|
773
|
+
# Extract slot names from output mapping
|
|
774
|
+
output_slots = set()
|
|
775
|
+
if KEY_MAPPING_OUTPUT in step.mapping and isinstance(
|
|
776
|
+
step.mapping[KEY_MAPPING_OUTPUT], list
|
|
777
|
+
):
|
|
778
|
+
for output_item in step.mapping[KEY_MAPPING_OUTPUT]:
|
|
779
|
+
output_slots.add(output_item[KEY_MAPPING_SLOT])
|
|
780
|
+
|
|
781
|
+
# Check for invalid slots in both input and output
|
|
782
|
+
invalid_input_slots = input_slots - domain_slots
|
|
783
|
+
invalid_output_slots = output_slots - domain_slots
|
|
784
|
+
|
|
785
|
+
# Raise exception if any invalid slots are found
|
|
786
|
+
if invalid_input_slots or invalid_output_slots:
|
|
787
|
+
raise InvalidMCPMappingSlotException(
|
|
788
|
+
step.id, flow.id, invalid_input_slots, invalid_output_slots
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def _get_call_steps_with_exit_if(
|
|
793
|
+
flows: "FlowsList",
|
|
794
|
+
) -> Iterator[Tuple["CallFlowStep", "Flow"]]:
|
|
795
|
+
"""Helper function to get all call steps with exit_if properties.
|
|
796
|
+
|
|
797
|
+
Args:
|
|
798
|
+
flows: The flows to search through.
|
|
799
|
+
|
|
800
|
+
Yields:
|
|
801
|
+
Tuples of (call_step, flow) for steps that have exit_if properties.
|
|
802
|
+
"""
|
|
803
|
+
for flow in flows.underlying_flows:
|
|
804
|
+
for step in flow.steps:
|
|
805
|
+
if isinstance(step, CallFlowStep) and step.exit_if:
|
|
806
|
+
yield step, flow
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def validate_exit_if_conditions(
|
|
810
|
+
flows: "FlowsList", domain: Optional["Domain"] = None
|
|
811
|
+
) -> None:
|
|
812
|
+
"""Validates that exit_if conditions are valid.
|
|
813
|
+
|
|
814
|
+
This function validates:
|
|
815
|
+
- Each condition contains at least one slot reference
|
|
816
|
+
- Only defined slots are used within the conditions
|
|
817
|
+
- The predicates are valid
|
|
818
|
+
|
|
819
|
+
Args:
|
|
820
|
+
flows: The flows to validate.
|
|
821
|
+
domain: The domain with slot definitions. If None, slot validation is skipped.
|
|
822
|
+
|
|
823
|
+
Raises:
|
|
824
|
+
InvalidExitIfConditionException: If any exit_if condition is invalid.
|
|
825
|
+
"""
|
|
826
|
+
for step, flow in _get_call_steps_with_exit_if(flows):
|
|
827
|
+
for condition in step.exit_if: # type: ignore[union-attr]
|
|
828
|
+
if not isinstance(condition, str):
|
|
829
|
+
raise InvalidExitIfConditionException(
|
|
830
|
+
step.id, flow.id, str(condition), "Condition must be a string"
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
# Check if condition contains at least one slot reference
|
|
834
|
+
slot_references_regex = re.compile(r"\bslots\.\w+")
|
|
835
|
+
slot_references = slot_references_regex.findall(condition)
|
|
836
|
+
if not slot_references:
|
|
837
|
+
raise InvalidExitIfConditionException(
|
|
838
|
+
step.id,
|
|
839
|
+
flow.id,
|
|
840
|
+
condition,
|
|
841
|
+
"Condition must contain at least one slot reference "
|
|
842
|
+
"(e.g., 'slots.slot_name')",
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
# Validate predicate syntax using pypred (always, regardless of domain)
|
|
846
|
+
_validate_predicate_syntax_with_pypred(step.id, flow.id, condition)
|
|
847
|
+
|
|
848
|
+
# Validate slot names if domain is provided
|
|
849
|
+
if domain:
|
|
850
|
+
domain_slots = {slot.name: slot for slot in domain.slots}
|
|
851
|
+
for slot_ref in slot_references:
|
|
852
|
+
slot_name = slot_ref.split(".")[1]
|
|
853
|
+
if slot_name not in domain_slots:
|
|
854
|
+
raise InvalidExitIfConditionException(
|
|
855
|
+
step.id,
|
|
856
|
+
flow.id,
|
|
857
|
+
condition,
|
|
858
|
+
f"Slot '{slot_name}' is not defined in the domain",
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def validate_exit_if_exclusivity(flows: "FlowsList") -> None:
|
|
863
|
+
"""Validates that call steps with exit_if don't have other properties.
|
|
864
|
+
|
|
865
|
+
This function validates that call steps with an exit_if property cannot have
|
|
866
|
+
any other properties besides the required 'call' property and standard flow
|
|
867
|
+
step properties (id, next, metadata, description).
|
|
868
|
+
|
|
869
|
+
Args:
|
|
870
|
+
flows: The flows to validate.
|
|
871
|
+
|
|
872
|
+
Raises:
|
|
873
|
+
ExitIfExclusivityException: If a call step with exit_if has other properties.
|
|
874
|
+
"""
|
|
875
|
+
for step, flow in _get_call_steps_with_exit_if(flows):
|
|
876
|
+
# Check for conflicting properties
|
|
877
|
+
conflicting_properties = []
|
|
878
|
+
|
|
879
|
+
# Check for MCP-related properties
|
|
880
|
+
if step.mcp_server is not None:
|
|
881
|
+
conflicting_properties.append("mcp_server")
|
|
882
|
+
if step.mapping is not None:
|
|
883
|
+
conflicting_properties.append("mapping")
|
|
884
|
+
|
|
885
|
+
if conflicting_properties:
|
|
886
|
+
raise ExitIfExclusivityException(step.id, flow.id, conflicting_properties)
|
|
574
887
|
|
|
575
888
|
|
|
576
889
|
def validate_linked_flows_exists(flows: "FlowsList") -> None:
|
|
@@ -594,7 +907,7 @@ def validate_linked_flows_exists(flows: "FlowsList") -> None:
|
|
|
594
907
|
flow.is_rasa_default_flow and step.link == RASA_PATTERN_CHITCHAT
|
|
595
908
|
)
|
|
596
909
|
):
|
|
597
|
-
raise
|
|
910
|
+
raise UnresolvedLinkFlowException(step.link, flow.id, step.id)
|
|
598
911
|
|
|
599
912
|
|
|
600
913
|
def validate_patterns_are_not_called_or_linked(flows: "FlowsList") -> None:
|
|
@@ -694,6 +1007,7 @@ def validate_slot_names_to_be_collected(flow: Flow) -> None:
|
|
|
694
1007
|
|
|
695
1008
|
def validate_flow_id(flow: Flow) -> None:
|
|
696
1009
|
"""Validates if the flow id comply with a specified regex.
|
|
1010
|
+
|
|
697
1011
|
Flow IDs can start with an alphanumeric character or an underscore.
|
|
698
1012
|
Followed by zero or more alphanumeric characters, hyphens, or underscores.
|
|
699
1013
|
|
|
@@ -751,3 +1065,53 @@ def validate_slot_persistence_configuration(flow: Flow) -> None:
|
|
|
751
1065
|
result = _is_persist_slots_valid(persist_slots, flow_slots)
|
|
752
1066
|
if not result.is_valid:
|
|
753
1067
|
raise InvalidPersistSlotsException(flow_id, result.invalid_slots)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def _validate_predicate_syntax_with_pypred(
|
|
1071
|
+
step_id: str, flow_id: str, condition: str
|
|
1072
|
+
) -> None:
|
|
1073
|
+
"""Validates predicate syntax using pypred.
|
|
1074
|
+
|
|
1075
|
+
This function validates that the exit_if condition has valid predicate syntax.
|
|
1076
|
+
Pypred catches syntax errors like double operators, invalid expressions, etc.
|
|
1077
|
+
|
|
1078
|
+
Args:
|
|
1079
|
+
step_id: The ID of the step containing the condition.
|
|
1080
|
+
flow_id: The ID of the flow containing the step.
|
|
1081
|
+
condition: The exit_if condition string.
|
|
1082
|
+
|
|
1083
|
+
Raises:
|
|
1084
|
+
InvalidExitIfConditionException: If pypred detects syntax errors.
|
|
1085
|
+
"""
|
|
1086
|
+
try:
|
|
1087
|
+
from rasa.utils.pypred import Predicate
|
|
1088
|
+
|
|
1089
|
+
# Create a simple test context for syntax validation.
|
|
1090
|
+
# This context works with all slot types since it's only used for:
|
|
1091
|
+
# 1. Template rendering: Basic structure for Jinja2 template rendering
|
|
1092
|
+
# 2. Syntax validation: Pypred validates predicate syntax
|
|
1093
|
+
# without actual slot values
|
|
1094
|
+
test_context = {"slots": {"test_slot": "test_value"}}
|
|
1095
|
+
rendered_template = Template(condition).render(test_context)
|
|
1096
|
+
|
|
1097
|
+
# Let pypred validate the predicate syntax
|
|
1098
|
+
predicate = Predicate(rendered_template)
|
|
1099
|
+
if not predicate.is_valid():
|
|
1100
|
+
raise InvalidExitIfConditionException(
|
|
1101
|
+
step_id,
|
|
1102
|
+
flow_id,
|
|
1103
|
+
condition,
|
|
1104
|
+
"Invalid predicate syntax",
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
except ImportError:
|
|
1108
|
+
# pypred not available, skip validation
|
|
1109
|
+
pass
|
|
1110
|
+
except Exception as e:
|
|
1111
|
+
# Re-raise pypred errors as predicate syntax errors
|
|
1112
|
+
raise InvalidExitIfConditionException(
|
|
1113
|
+
step_id,
|
|
1114
|
+
flow_id,
|
|
1115
|
+
condition,
|
|
1116
|
+
f"Invalid predicate syntax: {e}",
|
|
1117
|
+
)
|
|
@@ -127,7 +127,7 @@ class YAMLFlowsReader:
|
|
|
127
127
|
If a schema does not have a `schema_name` set, we will use the
|
|
128
128
|
`type` instead as a fallback.
|
|
129
129
|
"""
|
|
130
|
-
return schema.get("schema_name", schema.get("type"))
|
|
130
|
+
return str(schema.get("schema_name", schema.get("type") or "unknown"))
|
|
131
131
|
|
|
132
132
|
def schema_names(schemas: List[Dict[str, Any]]) -> List[str]:
|
|
133
133
|
"""Get the names of the schemas.
|
|
@@ -415,7 +415,8 @@ def process_yaml_content(yaml_content: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
415
415
|
|
|
416
416
|
# Under the "flows" key certain keys cannot have metadata
|
|
417
417
|
_process_keys_recursively(
|
|
418
|
-
yaml_content["flows"],
|
|
418
|
+
yaml_content["flows"],
|
|
419
|
+
["nlu_trigger", "set_slots", "metadata", "mapping", "exit_if"],
|
|
419
420
|
)
|
|
420
421
|
|
|
421
422
|
return yaml_content
|
rasa/shared/core/slots.py
CHANGED
|
@@ -68,9 +68,9 @@ class SlotRejection:
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
class SlotValidation(BaseModel):
|
|
71
|
-
rejections: List[SlotRejection] = Field(alias=REJECTIONS)
|
|
71
|
+
rejections: List[SlotRejection] = Field(alias=REJECTIONS) # type: ignore[literal-required]
|
|
72
72
|
"""how the slot value is validated using predicate evaluation."""
|
|
73
|
-
refill_utter: str = Field(alias=REFILL_UTTER)
|
|
73
|
+
refill_utter: str = Field(alias=REFILL_UTTER) # type: ignore[literal-required]
|
|
74
74
|
"""The utterance that the assistant uses to ask for the slot."""
|
|
75
75
|
|
|
76
76
|
@staticmethod
|
rasa/shared/core/trackers.py
CHANGED
|
@@ -849,7 +849,7 @@ class DialogueStateTracker:
|
|
|
849
849
|
action_names_to_exclude: Optional[List[Text]] = None,
|
|
850
850
|
skip: int = 0,
|
|
851
851
|
event_verbosity: EventVerbosity = EventVerbosity.APPLIED,
|
|
852
|
-
) -> Optional[
|
|
852
|
+
) -> Optional[Event]:
|
|
853
853
|
"""Gets the last event of a given type which was actually applied.
|
|
854
854
|
|
|
855
855
|
Args:
|
|
@@ -889,9 +889,12 @@ class DialogueStateTracker:
|
|
|
889
889
|
Returns:
|
|
890
890
|
`True` if last executed action had name `name`, otherwise `False`.
|
|
891
891
|
"""
|
|
892
|
-
|
|
892
|
+
last_event = self.get_last_event_for(
|
|
893
893
|
ActionExecuted, action_names_to_exclude=[ACTION_LISTEN_NAME], skip=skip
|
|
894
894
|
)
|
|
895
|
+
last: Optional[ActionExecuted] = (
|
|
896
|
+
last_event if isinstance(last_event, ActionExecuted) else None
|
|
897
|
+
)
|
|
895
898
|
return last is not None and last.action_name == name
|
|
896
899
|
|
|
897
900
|
###
|