rasa-pro 3.12.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- README.md +41 -0
- rasa/__init__.py +9 -0
- rasa/__main__.py +177 -0
- rasa/anonymization/__init__.py +2 -0
- rasa/anonymization/anonymisation_rule_yaml_reader.py +91 -0
- rasa/anonymization/anonymization_pipeline.py +286 -0
- rasa/anonymization/anonymization_rule_executor.py +260 -0
- rasa/anonymization/anonymization_rule_orchestrator.py +120 -0
- rasa/anonymization/schemas/config.yml +47 -0
- rasa/anonymization/utils.py +118 -0
- rasa/api.py +160 -0
- rasa/cli/__init__.py +5 -0
- rasa/cli/arguments/__init__.py +0 -0
- rasa/cli/arguments/data.py +106 -0
- rasa/cli/arguments/default_arguments.py +207 -0
- rasa/cli/arguments/evaluate.py +65 -0
- rasa/cli/arguments/export.py +51 -0
- rasa/cli/arguments/interactive.py +74 -0
- rasa/cli/arguments/run.py +219 -0
- rasa/cli/arguments/shell.py +17 -0
- rasa/cli/arguments/test.py +211 -0
- rasa/cli/arguments/train.py +279 -0
- rasa/cli/arguments/visualize.py +34 -0
- rasa/cli/arguments/x.py +30 -0
- rasa/cli/data.py +354 -0
- rasa/cli/dialogue_understanding_test.py +251 -0
- rasa/cli/e2e_test.py +259 -0
- rasa/cli/evaluate.py +222 -0
- rasa/cli/export.py +250 -0
- rasa/cli/inspect.py +75 -0
- rasa/cli/interactive.py +166 -0
- rasa/cli/license.py +65 -0
- rasa/cli/llm_fine_tuning.py +403 -0
- rasa/cli/markers.py +78 -0
- rasa/cli/project_templates/__init__.py +0 -0
- rasa/cli/project_templates/calm/actions/__init__.py +0 -0
- rasa/cli/project_templates/calm/actions/action_template.py +27 -0
- rasa/cli/project_templates/calm/actions/add_contact.py +30 -0
- rasa/cli/project_templates/calm/actions/db.py +57 -0
- rasa/cli/project_templates/calm/actions/list_contacts.py +22 -0
- rasa/cli/project_templates/calm/actions/remove_contact.py +35 -0
- rasa/cli/project_templates/calm/config.yml +10 -0
- rasa/cli/project_templates/calm/credentials.yml +33 -0
- rasa/cli/project_templates/calm/data/flows/add_contact.yml +31 -0
- rasa/cli/project_templates/calm/data/flows/list_contacts.yml +14 -0
- rasa/cli/project_templates/calm/data/flows/remove_contact.yml +29 -0
- rasa/cli/project_templates/calm/db/contacts.json +10 -0
- rasa/cli/project_templates/calm/domain/add_contact.yml +39 -0
- rasa/cli/project_templates/calm/domain/list_contacts.yml +17 -0
- rasa/cli/project_templates/calm/domain/remove_contact.yml +38 -0
- rasa/cli/project_templates/calm/domain/shared.yml +10 -0
- rasa/cli/project_templates/calm/e2e_tests/cancelations/user_cancels_during_a_correction.yml +16 -0
- rasa/cli/project_templates/calm/e2e_tests/cancelations/user_changes_mind_on_a_whim.yml +7 -0
- rasa/cli/project_templates/calm/e2e_tests/corrections/user_corrects_contact_handle.yml +20 -0
- rasa/cli/project_templates/calm/e2e_tests/corrections/user_corrects_contact_name.yml +19 -0
- rasa/cli/project_templates/calm/e2e_tests/happy_paths/user_adds_contact_to_their_list.yml +15 -0
- rasa/cli/project_templates/calm/e2e_tests/happy_paths/user_lists_contacts.yml +5 -0
- rasa/cli/project_templates/calm/e2e_tests/happy_paths/user_removes_contact.yml +11 -0
- rasa/cli/project_templates/calm/e2e_tests/happy_paths/user_removes_contact_from_list.yml +12 -0
- rasa/cli/project_templates/calm/endpoints.yml +58 -0
- rasa/cli/project_templates/default/actions/__init__.py +0 -0
- rasa/cli/project_templates/default/actions/actions.py +27 -0
- rasa/cli/project_templates/default/config.yml +44 -0
- rasa/cli/project_templates/default/credentials.yml +33 -0
- rasa/cli/project_templates/default/data/nlu.yml +91 -0
- rasa/cli/project_templates/default/data/rules.yml +13 -0
- rasa/cli/project_templates/default/data/stories.yml +30 -0
- rasa/cli/project_templates/default/domain.yml +34 -0
- rasa/cli/project_templates/default/endpoints.yml +42 -0
- rasa/cli/project_templates/default/tests/test_stories.yml +91 -0
- rasa/cli/project_templates/tutorial/actions/__init__.py +0 -0
- rasa/cli/project_templates/tutorial/actions/actions.py +22 -0
- rasa/cli/project_templates/tutorial/config.yml +12 -0
- rasa/cli/project_templates/tutorial/credentials.yml +33 -0
- rasa/cli/project_templates/tutorial/data/flows.yml +8 -0
- rasa/cli/project_templates/tutorial/data/patterns.yml +11 -0
- rasa/cli/project_templates/tutorial/domain.yml +35 -0
- rasa/cli/project_templates/tutorial/endpoints.yml +55 -0
- rasa/cli/run.py +143 -0
- rasa/cli/scaffold.py +273 -0
- rasa/cli/shell.py +141 -0
- rasa/cli/studio/__init__.py +0 -0
- rasa/cli/studio/download.py +62 -0
- rasa/cli/studio/studio.py +296 -0
- rasa/cli/studio/train.py +59 -0
- rasa/cli/studio/upload.py +62 -0
- rasa/cli/telemetry.py +102 -0
- rasa/cli/test.py +280 -0
- rasa/cli/train.py +278 -0
- rasa/cli/utils.py +484 -0
- rasa/cli/visualize.py +40 -0
- rasa/cli/x.py +206 -0
- rasa/constants.py +45 -0
- rasa/core/__init__.py +17 -0
- rasa/core/actions/__init__.py +0 -0
- rasa/core/actions/action.py +1318 -0
- rasa/core/actions/action_clean_stack.py +59 -0
- rasa/core/actions/action_exceptions.py +24 -0
- rasa/core/actions/action_hangup.py +29 -0
- rasa/core/actions/action_repeat_bot_messages.py +89 -0
- rasa/core/actions/action_run_slot_rejections.py +210 -0
- rasa/core/actions/action_trigger_chitchat.py +31 -0
- rasa/core/actions/action_trigger_flow.py +109 -0
- rasa/core/actions/action_trigger_search.py +31 -0
- rasa/core/actions/constants.py +5 -0
- rasa/core/actions/custom_action_executor.py +191 -0
- rasa/core/actions/direct_custom_actions_executor.py +109 -0
- rasa/core/actions/e2e_stub_custom_action_executor.py +72 -0
- rasa/core/actions/forms.py +741 -0
- rasa/core/actions/grpc_custom_action_executor.py +251 -0
- rasa/core/actions/http_custom_action_executor.py +145 -0
- rasa/core/actions/loops.py +114 -0
- rasa/core/actions/two_stage_fallback.py +186 -0
- rasa/core/agent.py +559 -0
- rasa/core/auth_retry_tracker_store.py +122 -0
- rasa/core/brokers/__init__.py +0 -0
- rasa/core/brokers/broker.py +126 -0
- rasa/core/brokers/file.py +58 -0
- rasa/core/brokers/kafka.py +324 -0
- rasa/core/brokers/pika.py +388 -0
- rasa/core/brokers/sql.py +86 -0
- rasa/core/channels/__init__.py +61 -0
- rasa/core/channels/botframework.py +338 -0
- rasa/core/channels/callback.py +84 -0
- rasa/core/channels/channel.py +456 -0
- rasa/core/channels/console.py +241 -0
- rasa/core/channels/development_inspector.py +197 -0
- rasa/core/channels/facebook.py +419 -0
- rasa/core/channels/hangouts.py +329 -0
- rasa/core/channels/inspector/.eslintrc.cjs +25 -0
- rasa/core/channels/inspector/.gitignore +23 -0
- rasa/core/channels/inspector/README.md +54 -0
- rasa/core/channels/inspector/assets/favicon.ico +0 -0
- rasa/core/channels/inspector/assets/rasa-chat.js +2 -0
- rasa/core/channels/inspector/custom.d.ts +3 -0
- rasa/core/channels/inspector/dist/assets/arc-861ddd57.js +1 -0
- rasa/core/channels/inspector/dist/assets/array-9f3ba611.js +1 -0
- rasa/core/channels/inspector/dist/assets/c4Diagram-d0fbc5ce-921f02db.js +10 -0
- rasa/core/channels/inspector/dist/assets/classDiagram-936ed81e-b436c4f8.js +2 -0
- rasa/core/channels/inspector/dist/assets/classDiagram-v2-c3cb15f1-511a23cb.js +2 -0
- rasa/core/channels/inspector/dist/assets/createText-62fc7601-ef476ecd.js +7 -0
- rasa/core/channels/inspector/dist/assets/edges-f2ad444c-f1878e0a.js +4 -0
- rasa/core/channels/inspector/dist/assets/erDiagram-9d236eb7-fac75185.js +51 -0
- rasa/core/channels/inspector/dist/assets/flowDb-1972c806-201c5bbc.js +6 -0
- rasa/core/channels/inspector/dist/assets/flowDiagram-7ea5b25a-f904ae41.js +4 -0
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-b080d6f2.js +1 -0
- rasa/core/channels/inspector/dist/assets/flowchart-elk-definition-abe16c3d-1813da66.js +139 -0
- rasa/core/channels/inspector/dist/assets/ganttDiagram-9b5ea136-872af172.js +266 -0
- rasa/core/channels/inspector/dist/assets/gitGraphDiagram-99d0ae7c-34a0af5a.js +70 -0
- rasa/core/channels/inspector/dist/assets/ibm-plex-mono-v4-latin-regular-128cfa44.ttf +0 -0
- rasa/core/channels/inspector/dist/assets/ibm-plex-mono-v4-latin-regular-21dbcb97.woff +0 -0
- rasa/core/channels/inspector/dist/assets/ibm-plex-mono-v4-latin-regular-222b5e26.svg +329 -0
- rasa/core/channels/inspector/dist/assets/ibm-plex-mono-v4-latin-regular-9ad89b2a.woff2 +0 -0
- rasa/core/channels/inspector/dist/assets/index-2c4b9a3b-42ba3e3d.js +1 -0
- rasa/core/channels/inspector/dist/assets/index-37817b51.js +1317 -0
- rasa/core/channels/inspector/dist/assets/index-3ee28881.css +1 -0
- rasa/core/channels/inspector/dist/assets/infoDiagram-736b4530-6b731386.js +7 -0
- rasa/core/channels/inspector/dist/assets/init-77b53fdd.js +1 -0
- rasa/core/channels/inspector/dist/assets/journeyDiagram-df861f2b-e8579ac6.js +139 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-700-60c05ee4.woff +0 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-700-8335d9b8.svg +438 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-700-9cc39c75.ttf +0 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-700-ead13ccf.woff2 +0 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-regular-16705655.woff2 +0 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-regular-5aeb07f9.woff +0 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-regular-9c459044.ttf +0 -0
- rasa/core/channels/inspector/dist/assets/lato-v14-latin-regular-9e2898a4.svg +435 -0
- rasa/core/channels/inspector/dist/assets/layout-89e6403a.js +1 -0
- rasa/core/channels/inspector/dist/assets/line-dc73d3fc.js +1 -0
- rasa/core/channels/inspector/dist/assets/linear-f5b1d2bc.js +1 -0
- rasa/core/channels/inspector/dist/assets/mindmap-definition-beec6740-82cb74fa.js +109 -0
- rasa/core/channels/inspector/dist/assets/ordinal-ba9b4969.js +1 -0
- rasa/core/channels/inspector/dist/assets/path-53f90ab3.js +1 -0
- rasa/core/channels/inspector/dist/assets/pieDiagram-dbbf0591-bdf5f29b.js +35 -0
- rasa/core/channels/inspector/dist/assets/quadrantDiagram-4d7f4fd6-c7a0cbe4.js +7 -0
- rasa/core/channels/inspector/dist/assets/requirementDiagram-6fc4c22a-7ec5410f.js +52 -0
- rasa/core/channels/inspector/dist/assets/sankeyDiagram-8f13d901-caee5554.js +8 -0
- rasa/core/channels/inspector/dist/assets/sequenceDiagram-b655622a-2935f8db.js +122 -0
- rasa/core/channels/inspector/dist/assets/stateDiagram-59f0c015-8f5d9693.js +1 -0
- rasa/core/channels/inspector/dist/assets/stateDiagram-v2-2b26beab-d565d1de.js +1 -0
- rasa/core/channels/inspector/dist/assets/styles-080da4f6-75ad421d.js +110 -0
- rasa/core/channels/inspector/dist/assets/styles-3dcbcfbf-7e764226.js +159 -0
- rasa/core/channels/inspector/dist/assets/styles-9c745c82-7a4e0e61.js +207 -0
- rasa/core/channels/inspector/dist/assets/svgDrawCommon-4835440b-4019d1bf.js +1 -0
- rasa/core/channels/inspector/dist/assets/timeline-definition-5b62e21b-01ea12df.js +61 -0
- rasa/core/channels/inspector/dist/assets/xychartDiagram-2b33534f-89407137.js +7 -0
- rasa/core/channels/inspector/dist/index.html +42 -0
- rasa/core/channels/inspector/index.html +40 -0
- rasa/core/channels/inspector/jest.config.ts +13 -0
- rasa/core/channels/inspector/package.json +52 -0
- rasa/core/channels/inspector/setupTests.ts +2 -0
- rasa/core/channels/inspector/src/App.tsx +220 -0
- rasa/core/channels/inspector/src/components/Chat.tsx +95 -0
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +108 -0
- rasa/core/channels/inspector/src/components/DialogueInformation.tsx +187 -0
- rasa/core/channels/inspector/src/components/DialogueStack.tsx +136 -0
- rasa/core/channels/inspector/src/components/ExpandIcon.tsx +16 -0
- rasa/core/channels/inspector/src/components/FullscreenButton.tsx +45 -0
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +22 -0
- rasa/core/channels/inspector/src/components/NoActiveFlow.tsx +21 -0
- rasa/core/channels/inspector/src/components/RasaLogo.tsx +32 -0
- rasa/core/channels/inspector/src/components/SaraDiagrams.tsx +39 -0
- rasa/core/channels/inspector/src/components/Slots.tsx +91 -0
- rasa/core/channels/inspector/src/components/Welcome.tsx +54 -0
- rasa/core/channels/inspector/src/helpers/audiostream.ts +191 -0
- rasa/core/channels/inspector/src/helpers/formatters.test.ts +392 -0
- rasa/core/channels/inspector/src/helpers/formatters.ts +306 -0
- rasa/core/channels/inspector/src/helpers/utils.ts +127 -0
- rasa/core/channels/inspector/src/main.tsx +13 -0
- rasa/core/channels/inspector/src/theme/Button/Button.ts +29 -0
- rasa/core/channels/inspector/src/theme/Heading/Heading.ts +31 -0
- rasa/core/channels/inspector/src/theme/Input/Input.ts +27 -0
- rasa/core/channels/inspector/src/theme/Link/Link.ts +10 -0
- rasa/core/channels/inspector/src/theme/Modal/Modal.ts +47 -0
- rasa/core/channels/inspector/src/theme/Table/Table.tsx +38 -0
- rasa/core/channels/inspector/src/theme/Tooltip/Tooltip.ts +12 -0
- rasa/core/channels/inspector/src/theme/base/breakpoints.ts +8 -0
- rasa/core/channels/inspector/src/theme/base/colors.ts +88 -0
- rasa/core/channels/inspector/src/theme/base/fonts/fontFaces.css +29 -0
- rasa/core/channels/inspector/src/theme/base/fonts/ibm-plex-mono-v4-latin/ibm-plex-mono-v4-latin-regular.eot +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/ibm-plex-mono-v4-latin/ibm-plex-mono-v4-latin-regular.svg +329 -0
- rasa/core/channels/inspector/src/theme/base/fonts/ibm-plex-mono-v4-latin/ibm-plex-mono-v4-latin-regular.ttf +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/ibm-plex-mono-v4-latin/ibm-plex-mono-v4-latin-regular.woff +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/ibm-plex-mono-v4-latin/ibm-plex-mono-v4-latin-regular.woff2 +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-700.eot +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-700.svg +438 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-700.ttf +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-700.woff +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-700.woff2 +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-regular.eot +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-regular.svg +435 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-regular.ttf +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-regular.woff +0 -0
- rasa/core/channels/inspector/src/theme/base/fonts/lato-v14-latin/lato-v14-latin-regular.woff2 +0 -0
- rasa/core/channels/inspector/src/theme/base/radii.ts +9 -0
- rasa/core/channels/inspector/src/theme/base/shadows.ts +7 -0
- rasa/core/channels/inspector/src/theme/base/sizes.ts +7 -0
- rasa/core/channels/inspector/src/theme/base/space.ts +15 -0
- rasa/core/channels/inspector/src/theme/base/styles.ts +13 -0
- rasa/core/channels/inspector/src/theme/base/typography.ts +24 -0
- rasa/core/channels/inspector/src/theme/base/zIndices.ts +19 -0
- rasa/core/channels/inspector/src/theme/index.ts +101 -0
- rasa/core/channels/inspector/src/types.ts +84 -0
- rasa/core/channels/inspector/src/vite-env.d.ts +1 -0
- rasa/core/channels/inspector/tests/__mocks__/fileMock.ts +1 -0
- rasa/core/channels/inspector/tests/__mocks__/matchMedia.ts +16 -0
- rasa/core/channels/inspector/tests/__mocks__/styleMock.ts +1 -0
- rasa/core/channels/inspector/tests/renderWithProviders.tsx +14 -0
- rasa/core/channels/inspector/tsconfig.json +26 -0
- rasa/core/channels/inspector/tsconfig.node.json +10 -0
- rasa/core/channels/inspector/vite.config.ts +8 -0
- rasa/core/channels/inspector/yarn.lock +6249 -0
- rasa/core/channels/mattermost.py +229 -0
- rasa/core/channels/rasa_chat.py +126 -0
- rasa/core/channels/rest.py +230 -0
- rasa/core/channels/rocketchat.py +174 -0
- rasa/core/channels/slack.py +620 -0
- rasa/core/channels/socketio.py +302 -0
- rasa/core/channels/telegram.py +298 -0
- rasa/core/channels/twilio.py +169 -0
- rasa/core/channels/vier_cvg.py +374 -0
- rasa/core/channels/voice_ready/__init__.py +0 -0
- rasa/core/channels/voice_ready/audiocodes.py +501 -0
- rasa/core/channels/voice_ready/jambonz.py +121 -0
- rasa/core/channels/voice_ready/jambonz_protocol.py +396 -0
- rasa/core/channels/voice_ready/twilio_voice.py +403 -0
- rasa/core/channels/voice_ready/utils.py +37 -0
- rasa/core/channels/voice_stream/__init__.py +0 -0
- rasa/core/channels/voice_stream/asr/__init__.py +0 -0
- rasa/core/channels/voice_stream/asr/asr_engine.py +89 -0
- rasa/core/channels/voice_stream/asr/asr_event.py +18 -0
- rasa/core/channels/voice_stream/asr/azure.py +130 -0
- rasa/core/channels/voice_stream/asr/deepgram.py +90 -0
- rasa/core/channels/voice_stream/audio_bytes.py +8 -0
- rasa/core/channels/voice_stream/browser_audio.py +107 -0
- rasa/core/channels/voice_stream/call_state.py +23 -0
- rasa/core/channels/voice_stream/tts/__init__.py +0 -0
- rasa/core/channels/voice_stream/tts/azure.py +106 -0
- rasa/core/channels/voice_stream/tts/cartesia.py +118 -0
- rasa/core/channels/voice_stream/tts/tts_cache.py +27 -0
- rasa/core/channels/voice_stream/tts/tts_engine.py +58 -0
- rasa/core/channels/voice_stream/twilio_media_streams.py +173 -0
- rasa/core/channels/voice_stream/util.py +57 -0
- rasa/core/channels/voice_stream/voice_channel.py +427 -0
- rasa/core/channels/webexteams.py +134 -0
- rasa/core/concurrent_lock_store.py +210 -0
- rasa/core/constants.py +112 -0
- rasa/core/evaluation/__init__.py +0 -0
- rasa/core/evaluation/marker.py +267 -0
- rasa/core/evaluation/marker_base.py +923 -0
- rasa/core/evaluation/marker_stats.py +293 -0
- rasa/core/evaluation/marker_tracker_loader.py +103 -0
- rasa/core/exceptions.py +29 -0
- rasa/core/exporter.py +284 -0
- rasa/core/featurizers/__init__.py +0 -0
- rasa/core/featurizers/precomputation.py +410 -0
- rasa/core/featurizers/single_state_featurizer.py +421 -0
- rasa/core/featurizers/tracker_featurizers.py +1262 -0
- rasa/core/http_interpreter.py +89 -0
- rasa/core/information_retrieval/__init__.py +7 -0
- rasa/core/information_retrieval/faiss.py +124 -0
- rasa/core/information_retrieval/information_retrieval.py +137 -0
- rasa/core/information_retrieval/milvus.py +59 -0
- rasa/core/information_retrieval/qdrant.py +96 -0
- rasa/core/jobs.py +63 -0
- rasa/core/lock.py +139 -0
- rasa/core/lock_store.py +343 -0
- rasa/core/migrate.py +403 -0
- rasa/core/nlg/__init__.py +3 -0
- rasa/core/nlg/callback.py +146 -0
- rasa/core/nlg/contextual_response_rephraser.py +320 -0
- rasa/core/nlg/generator.py +230 -0
- rasa/core/nlg/interpolator.py +143 -0
- rasa/core/nlg/response.py +155 -0
- rasa/core/nlg/summarize.py +70 -0
- rasa/core/persistor.py +538 -0
- rasa/core/policies/__init__.py +0 -0
- rasa/core/policies/ensemble.py +329 -0
- rasa/core/policies/enterprise_search_policy.py +905 -0
- rasa/core/policies/enterprise_search_prompt_template.jinja2 +25 -0
- rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +60 -0
- rasa/core/policies/flow_policy.py +205 -0
- rasa/core/policies/flows/__init__.py +0 -0
- rasa/core/policies/flows/flow_exceptions.py +44 -0
- rasa/core/policies/flows/flow_executor.py +754 -0
- rasa/core/policies/flows/flow_step_result.py +43 -0
- rasa/core/policies/intentless_policy.py +1031 -0
- rasa/core/policies/intentless_prompt_template.jinja2 +22 -0
- rasa/core/policies/memoization.py +538 -0
- rasa/core/policies/policy.py +725 -0
- rasa/core/policies/rule_policy.py +1273 -0
- rasa/core/policies/ted_policy.py +2169 -0
- rasa/core/policies/unexpected_intent_policy.py +1022 -0
- rasa/core/processor.py +1465 -0
- rasa/core/run.py +342 -0
- rasa/core/secrets_manager/__init__.py +0 -0
- rasa/core/secrets_manager/constants.py +36 -0
- rasa/core/secrets_manager/endpoints.py +391 -0
- rasa/core/secrets_manager/factory.py +241 -0
- rasa/core/secrets_manager/secret_manager.py +262 -0
- rasa/core/secrets_manager/vault.py +584 -0
- rasa/core/test.py +1335 -0
- rasa/core/tracker_store.py +1703 -0
- rasa/core/train.py +105 -0
- rasa/core/training/__init__.py +89 -0
- rasa/core/training/converters/__init__.py +0 -0
- rasa/core/training/converters/responses_prefix_converter.py +119 -0
- rasa/core/training/interactive.py +1744 -0
- rasa/core/training/story_conflict.py +381 -0
- rasa/core/training/training.py +93 -0
- rasa/core/utils.py +366 -0
- rasa/core/visualize.py +70 -0
- rasa/dialogue_understanding/__init__.py +0 -0
- rasa/dialogue_understanding/coexistence/__init__.py +0 -0
- rasa/dialogue_understanding/coexistence/constants.py +4 -0
- rasa/dialogue_understanding/coexistence/intent_based_router.py +196 -0
- rasa/dialogue_understanding/coexistence/llm_based_router.py +327 -0
- rasa/dialogue_understanding/coexistence/router_template.jinja2 +12 -0
- rasa/dialogue_understanding/commands/__init__.py +61 -0
- rasa/dialogue_understanding/commands/can_not_handle_command.py +70 -0
- rasa/dialogue_understanding/commands/cancel_flow_command.py +125 -0
- rasa/dialogue_understanding/commands/change_flow_command.py +44 -0
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +57 -0
- rasa/dialogue_understanding/commands/clarify_command.py +86 -0
- rasa/dialogue_understanding/commands/command.py +85 -0
- rasa/dialogue_understanding/commands/correct_slots_command.py +297 -0
- rasa/dialogue_understanding/commands/error_command.py +79 -0
- rasa/dialogue_understanding/commands/free_form_answer_command.py +9 -0
- rasa/dialogue_understanding/commands/handle_code_change_command.py +73 -0
- rasa/dialogue_understanding/commands/human_handoff_command.py +66 -0
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +57 -0
- rasa/dialogue_understanding/commands/noop_command.py +54 -0
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +60 -0
- rasa/dialogue_understanding/commands/restart_command.py +58 -0
- rasa/dialogue_understanding/commands/session_end_command.py +61 -0
- rasa/dialogue_understanding/commands/session_start_command.py +59 -0
- rasa/dialogue_understanding/commands/set_slot_command.py +160 -0
- rasa/dialogue_understanding/commands/skip_question_command.py +75 -0
- rasa/dialogue_understanding/commands/start_flow_command.py +107 -0
- rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
- rasa/dialogue_understanding/commands/utils.py +45 -0
- rasa/dialogue_understanding/generator/__init__.py +21 -0
- rasa/dialogue_understanding/generator/command_generator.py +464 -0
- rasa/dialogue_understanding/generator/constants.py +27 -0
- rasa/dialogue_understanding/generator/flow_document_template.jinja2 +4 -0
- rasa/dialogue_understanding/generator/flow_retrieval.py +466 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +500 -0
- rasa/dialogue_understanding/generator/llm_command_generator.py +67 -0
- rasa/dialogue_understanding/generator/multi_step/__init__.py +0 -0
- rasa/dialogue_understanding/generator/multi_step/fill_slots_prompt.jinja2 +62 -0
- rasa/dialogue_understanding/generator/multi_step/handle_flows_prompt.jinja2 +38 -0
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +920 -0
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +261 -0
- rasa/dialogue_understanding/generator/single_step/__init__.py +0 -0
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +60 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +486 -0
- rasa/dialogue_understanding/patterns/__init__.py +0 -0
- rasa/dialogue_understanding/patterns/cancel.py +111 -0
- rasa/dialogue_understanding/patterns/cannot_handle.py +43 -0
- rasa/dialogue_understanding/patterns/chitchat.py +37 -0
- rasa/dialogue_understanding/patterns/clarify.py +97 -0
- rasa/dialogue_understanding/patterns/code_change.py +41 -0
- rasa/dialogue_understanding/patterns/collect_information.py +90 -0
- rasa/dialogue_understanding/patterns/completed.py +40 -0
- rasa/dialogue_understanding/patterns/continue_interrupted.py +42 -0
- rasa/dialogue_understanding/patterns/correction.py +278 -0
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +301 -0
- rasa/dialogue_understanding/patterns/human_handoff.py +37 -0
- rasa/dialogue_understanding/patterns/internal_error.py +47 -0
- rasa/dialogue_understanding/patterns/repeat.py +37 -0
- rasa/dialogue_understanding/patterns/restart.py +37 -0
- rasa/dialogue_understanding/patterns/search.py +37 -0
- rasa/dialogue_understanding/patterns/session_start.py +37 -0
- rasa/dialogue_understanding/patterns/skip_question.py +38 -0
- rasa/dialogue_understanding/patterns/user_silence.py +37 -0
- rasa/dialogue_understanding/processor/__init__.py +0 -0
- rasa/dialogue_understanding/processor/command_processor.py +720 -0
- rasa/dialogue_understanding/processor/command_processor_component.py +43 -0
- rasa/dialogue_understanding/stack/__init__.py +0 -0
- rasa/dialogue_understanding/stack/dialogue_stack.py +178 -0
- rasa/dialogue_understanding/stack/frames/__init__.py +19 -0
- rasa/dialogue_understanding/stack/frames/chit_chat_frame.py +27 -0
- rasa/dialogue_understanding/stack/frames/dialogue_stack_frame.py +137 -0
- rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +157 -0
- rasa/dialogue_understanding/stack/frames/pattern_frame.py +10 -0
- rasa/dialogue_understanding/stack/frames/search_frame.py +27 -0
- rasa/dialogue_understanding/stack/utils.py +211 -0
- rasa/dialogue_understanding/utils.py +14 -0
- rasa/dialogue_understanding_test/__init__.py +0 -0
- rasa/dialogue_understanding_test/command_metric_calculation.py +12 -0
- rasa/dialogue_understanding_test/constants.py +17 -0
- rasa/dialogue_understanding_test/du_test_case.py +118 -0
- rasa/dialogue_understanding_test/du_test_result.py +11 -0
- rasa/dialogue_understanding_test/du_test_runner.py +93 -0
- rasa/dialogue_understanding_test/io.py +54 -0
- rasa/dialogue_understanding_test/validation.py +22 -0
- rasa/e2e_test/__init__.py +0 -0
- rasa/e2e_test/aggregate_test_stats_calculator.py +134 -0
- rasa/e2e_test/assertions.py +1345 -0
- rasa/e2e_test/assertions_schema.yml +129 -0
- rasa/e2e_test/constants.py +31 -0
- rasa/e2e_test/e2e_config.py +220 -0
- rasa/e2e_test/e2e_config_schema.yml +26 -0
- rasa/e2e_test/e2e_test_case.py +569 -0
- rasa/e2e_test/e2e_test_converter.py +363 -0
- rasa/e2e_test/e2e_test_converter_prompt.jinja2 +70 -0
- rasa/e2e_test/e2e_test_coverage_report.py +364 -0
- rasa/e2e_test/e2e_test_result.py +54 -0
- rasa/e2e_test/e2e_test_runner.py +1192 -0
- rasa/e2e_test/e2e_test_schema.yml +181 -0
- rasa/e2e_test/pykwalify_extensions.py +39 -0
- rasa/e2e_test/stub_custom_action.py +70 -0
- rasa/e2e_test/utils/__init__.py +0 -0
- rasa/e2e_test/utils/e2e_yaml_utils.py +55 -0
- rasa/e2e_test/utils/io.py +598 -0
- rasa/e2e_test/utils/validation.py +178 -0
- rasa/engine/__init__.py +0 -0
- rasa/engine/caching.py +463 -0
- rasa/engine/constants.py +17 -0
- rasa/engine/exceptions.py +14 -0
- rasa/engine/graph.py +642 -0
- rasa/engine/loader.py +48 -0
- rasa/engine/recipes/__init__.py +0 -0
- rasa/engine/recipes/config_files/default_config.yml +41 -0
- rasa/engine/recipes/default_components.py +97 -0
- rasa/engine/recipes/default_recipe.py +1272 -0
- rasa/engine/recipes/graph_recipe.py +79 -0
- rasa/engine/recipes/recipe.py +93 -0
- rasa/engine/runner/__init__.py +0 -0
- rasa/engine/runner/dask.py +250 -0
- rasa/engine/runner/interface.py +49 -0
- rasa/engine/storage/__init__.py +0 -0
- rasa/engine/storage/local_model_storage.py +244 -0
- rasa/engine/storage/resource.py +110 -0
- rasa/engine/storage/storage.py +199 -0
- rasa/engine/training/__init__.py +0 -0
- rasa/engine/training/components.py +176 -0
- rasa/engine/training/fingerprinting.py +64 -0
- rasa/engine/training/graph_trainer.py +256 -0
- rasa/engine/training/hooks.py +164 -0
- rasa/engine/validation.py +1451 -0
- rasa/env.py +14 -0
- rasa/exceptions.py +69 -0
- rasa/graph_components/__init__.py +0 -0
- rasa/graph_components/converters/__init__.py +0 -0
- rasa/graph_components/converters/nlu_message_converter.py +48 -0
- rasa/graph_components/providers/__init__.py +0 -0
- rasa/graph_components/providers/domain_for_core_training_provider.py +87 -0
- rasa/graph_components/providers/domain_provider.py +71 -0
- rasa/graph_components/providers/flows_provider.py +74 -0
- rasa/graph_components/providers/forms_provider.py +44 -0
- rasa/graph_components/providers/nlu_training_data_provider.py +56 -0
- rasa/graph_components/providers/responses_provider.py +44 -0
- rasa/graph_components/providers/rule_only_provider.py +49 -0
- rasa/graph_components/providers/story_graph_provider.py +96 -0
- rasa/graph_components/providers/training_tracker_provider.py +55 -0
- rasa/graph_components/validators/__init__.py +0 -0
- rasa/graph_components/validators/default_recipe_validator.py +550 -0
- rasa/graph_components/validators/finetuning_validator.py +302 -0
- rasa/hooks.py +111 -0
- rasa/jupyter.py +63 -0
- rasa/llm_fine_tuning/__init__.py +0 -0
- rasa/llm_fine_tuning/annotation_module.py +241 -0
- rasa/llm_fine_tuning/conversations.py +144 -0
- rasa/llm_fine_tuning/llm_data_preparation_module.py +178 -0
- rasa/llm_fine_tuning/paraphrasing/__init__.py +0 -0
- rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +281 -0
- rasa/llm_fine_tuning/paraphrasing/default_rephrase_prompt_template.jina2 +44 -0
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +121 -0
- rasa/llm_fine_tuning/paraphrasing/rephrased_user_message.py +10 -0
- rasa/llm_fine_tuning/paraphrasing_module.py +128 -0
- rasa/llm_fine_tuning/storage.py +174 -0
- rasa/llm_fine_tuning/train_test_split_module.py +441 -0
- rasa/markers/__init__.py +0 -0
- rasa/markers/marker.py +269 -0
- rasa/markers/marker_base.py +828 -0
- rasa/markers/upload.py +74 -0
- rasa/markers/validate.py +21 -0
- rasa/model.py +118 -0
- rasa/model_manager/__init__.py +0 -0
- rasa/model_manager/config.py +40 -0
- rasa/model_manager/model_api.py +559 -0
- rasa/model_manager/runner_service.py +286 -0
- rasa/model_manager/socket_bridge.py +146 -0
- rasa/model_manager/studio_jwt_auth.py +86 -0
- rasa/model_manager/trainer_service.py +325 -0
- rasa/model_manager/utils.py +87 -0
- rasa/model_manager/warm_rasa_process.py +187 -0
- rasa/model_service.py +112 -0
- rasa/model_testing.py +457 -0
- rasa/model_training.py +596 -0
- rasa/nlu/__init__.py +7 -0
- rasa/nlu/classifiers/__init__.py +3 -0
- rasa/nlu/classifiers/classifier.py +5 -0
- rasa/nlu/classifiers/diet_classifier.py +1881 -0
- rasa/nlu/classifiers/fallback_classifier.py +192 -0
- rasa/nlu/classifiers/keyword_intent_classifier.py +188 -0
- rasa/nlu/classifiers/logistic_regression_classifier.py +253 -0
- rasa/nlu/classifiers/mitie_intent_classifier.py +156 -0
- rasa/nlu/classifiers/regex_message_handler.py +56 -0
- rasa/nlu/classifiers/sklearn_intent_classifier.py +330 -0
- rasa/nlu/constants.py +77 -0
- rasa/nlu/convert.py +40 -0
- rasa/nlu/emulators/__init__.py +0 -0
- rasa/nlu/emulators/dialogflow.py +55 -0
- rasa/nlu/emulators/emulator.py +49 -0
- rasa/nlu/emulators/luis.py +86 -0
- rasa/nlu/emulators/no_emulator.py +10 -0
- rasa/nlu/emulators/wit.py +56 -0
- rasa/nlu/extractors/__init__.py +0 -0
- rasa/nlu/extractors/crf_entity_extractor.py +715 -0
- rasa/nlu/extractors/duckling_entity_extractor.py +206 -0
- rasa/nlu/extractors/entity_synonyms.py +178 -0
- rasa/nlu/extractors/extractor.py +470 -0
- rasa/nlu/extractors/mitie_entity_extractor.py +293 -0
- rasa/nlu/extractors/regex_entity_extractor.py +220 -0
- rasa/nlu/extractors/spacy_entity_extractor.py +95 -0
- rasa/nlu/featurizers/__init__.py +0 -0
- rasa/nlu/featurizers/dense_featurizer/__init__.py +0 -0
- rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +445 -0
- rasa/nlu/featurizers/dense_featurizer/dense_featurizer.py +57 -0
- rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +768 -0
- rasa/nlu/featurizers/dense_featurizer/mitie_featurizer.py +170 -0
- rasa/nlu/featurizers/dense_featurizer/spacy_featurizer.py +132 -0
- rasa/nlu/featurizers/featurizer.py +89 -0
- rasa/nlu/featurizers/sparse_featurizer/__init__.py +0 -0
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +867 -0
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +571 -0
- rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +271 -0
- rasa/nlu/featurizers/sparse_featurizer/sparse_featurizer.py +9 -0
- rasa/nlu/model.py +24 -0
- rasa/nlu/run.py +27 -0
- rasa/nlu/selectors/__init__.py +0 -0
- rasa/nlu/selectors/response_selector.py +987 -0
- rasa/nlu/test.py +1940 -0
- rasa/nlu/tokenizers/__init__.py +0 -0
- rasa/nlu/tokenizers/jieba_tokenizer.py +148 -0
- rasa/nlu/tokenizers/mitie_tokenizer.py +75 -0
- rasa/nlu/tokenizers/spacy_tokenizer.py +72 -0
- rasa/nlu/tokenizers/tokenizer.py +239 -0
- rasa/nlu/tokenizers/whitespace_tokenizer.py +95 -0
- rasa/nlu/utils/__init__.py +35 -0
- rasa/nlu/utils/bilou_utils.py +462 -0
- rasa/nlu/utils/hugging_face/__init__.py +0 -0
- rasa/nlu/utils/hugging_face/registry.py +108 -0
- rasa/nlu/utils/hugging_face/transformers_pre_post_processors.py +311 -0
- rasa/nlu/utils/mitie_utils.py +113 -0
- rasa/nlu/utils/pattern_utils.py +168 -0
- rasa/nlu/utils/spacy_utils.py +310 -0
- rasa/plugin.py +90 -0
- rasa/server.py +1588 -0
- rasa/shared/__init__.py +0 -0
- rasa/shared/constants.py +311 -0
- rasa/shared/core/__init__.py +0 -0
- rasa/shared/core/command_payload_reader.py +109 -0
- rasa/shared/core/constants.py +180 -0
- rasa/shared/core/conversation.py +46 -0
- rasa/shared/core/domain.py +2172 -0
- rasa/shared/core/events.py +2559 -0
- rasa/shared/core/flows/__init__.py +7 -0
- rasa/shared/core/flows/flow.py +562 -0
- rasa/shared/core/flows/flow_path.py +84 -0
- rasa/shared/core/flows/flow_step.py +146 -0
- rasa/shared/core/flows/flow_step_links.py +319 -0
- rasa/shared/core/flows/flow_step_sequence.py +70 -0
- rasa/shared/core/flows/flows_list.py +258 -0
- rasa/shared/core/flows/flows_yaml_schema.json +303 -0
- rasa/shared/core/flows/nlu_trigger.py +117 -0
- rasa/shared/core/flows/steps/__init__.py +24 -0
- rasa/shared/core/flows/steps/action.py +56 -0
- rasa/shared/core/flows/steps/call.py +64 -0
- rasa/shared/core/flows/steps/collect.py +112 -0
- rasa/shared/core/flows/steps/constants.py +5 -0
- rasa/shared/core/flows/steps/continuation.py +36 -0
- rasa/shared/core/flows/steps/end.py +22 -0
- rasa/shared/core/flows/steps/internal.py +44 -0
- rasa/shared/core/flows/steps/link.py +51 -0
- rasa/shared/core/flows/steps/no_operation.py +48 -0
- rasa/shared/core/flows/steps/set_slots.py +50 -0
- rasa/shared/core/flows/steps/start.py +30 -0
- rasa/shared/core/flows/utils.py +39 -0
- rasa/shared/core/flows/validation.py +735 -0
- rasa/shared/core/flows/yaml_flows_io.py +405 -0
- rasa/shared/core/generator.py +908 -0
- rasa/shared/core/slot_mappings.py +526 -0
- rasa/shared/core/slots.py +654 -0
- rasa/shared/core/trackers.py +1183 -0
- rasa/shared/core/training_data/__init__.py +0 -0
- rasa/shared/core/training_data/loading.py +89 -0
- rasa/shared/core/training_data/story_reader/__init__.py +0 -0
- rasa/shared/core/training_data/story_reader/story_reader.py +129 -0
- rasa/shared/core/training_data/story_reader/story_step_builder.py +168 -0
- rasa/shared/core/training_data/story_reader/yaml_story_reader.py +888 -0
- rasa/shared/core/training_data/story_writer/__init__.py +0 -0
- rasa/shared/core/training_data/story_writer/story_writer.py +76 -0
- rasa/shared/core/training_data/story_writer/yaml_story_writer.py +444 -0
- rasa/shared/core/training_data/structures.py +858 -0
- rasa/shared/core/training_data/visualization.html +146 -0
- rasa/shared/core/training_data/visualization.py +603 -0
- rasa/shared/data.py +249 -0
- rasa/shared/engine/__init__.py +0 -0
- rasa/shared/engine/caching.py +26 -0
- rasa/shared/exceptions.py +167 -0
- rasa/shared/importers/__init__.py +0 -0
- rasa/shared/importers/importer.py +770 -0
- rasa/shared/importers/multi_project.py +215 -0
- rasa/shared/importers/rasa.py +108 -0
- rasa/shared/importers/remote_importer.py +196 -0
- rasa/shared/importers/utils.py +36 -0
- rasa/shared/nlu/__init__.py +0 -0
- rasa/shared/nlu/constants.py +53 -0
- rasa/shared/nlu/interpreter.py +10 -0
- rasa/shared/nlu/training_data/__init__.py +0 -0
- rasa/shared/nlu/training_data/entities_parser.py +208 -0
- rasa/shared/nlu/training_data/features.py +492 -0
- rasa/shared/nlu/training_data/formats/__init__.py +10 -0
- rasa/shared/nlu/training_data/formats/dialogflow.py +163 -0
- rasa/shared/nlu/training_data/formats/luis.py +87 -0
- rasa/shared/nlu/training_data/formats/rasa.py +135 -0
- rasa/shared/nlu/training_data/formats/rasa_yaml.py +618 -0
- rasa/shared/nlu/training_data/formats/readerwriter.py +244 -0
- rasa/shared/nlu/training_data/formats/wit.py +52 -0
- rasa/shared/nlu/training_data/loading.py +137 -0
- rasa/shared/nlu/training_data/lookup_tables_parser.py +30 -0
- rasa/shared/nlu/training_data/message.py +490 -0
- rasa/shared/nlu/training_data/schemas/__init__.py +0 -0
- rasa/shared/nlu/training_data/schemas/data_schema.py +85 -0
- rasa/shared/nlu/training_data/schemas/nlu.yml +53 -0
- rasa/shared/nlu/training_data/schemas/responses.yml +70 -0
- rasa/shared/nlu/training_data/synonyms_parser.py +42 -0
- rasa/shared/nlu/training_data/training_data.py +729 -0
- rasa/shared/nlu/training_data/util.py +223 -0
- rasa/shared/providers/__init__.py +0 -0
- rasa/shared/providers/_configs/__init__.py +0 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +677 -0
- rasa/shared/providers/_configs/client_config.py +59 -0
- rasa/shared/providers/_configs/default_litellm_client_config.py +132 -0
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +236 -0
- rasa/shared/providers/_configs/litellm_router_client_config.py +222 -0
- rasa/shared/providers/_configs/model_group_config.py +173 -0
- rasa/shared/providers/_configs/openai_client_config.py +177 -0
- rasa/shared/providers/_configs/rasa_llm_client_config.py +75 -0
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +178 -0
- rasa/shared/providers/_configs/utils.py +117 -0
- rasa/shared/providers/_ssl_verification_utils.py +124 -0
- rasa/shared/providers/_utils.py +79 -0
- rasa/shared/providers/constants.py +7 -0
- rasa/shared/providers/embedding/__init__.py +0 -0
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +243 -0
- rasa/shared/providers/embedding/_langchain_embedding_client_adapter.py +74 -0
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +335 -0
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +126 -0
- rasa/shared/providers/embedding/embedding_client.py +90 -0
- rasa/shared/providers/embedding/embedding_response.py +41 -0
- rasa/shared/providers/embedding/huggingface_local_embedding_client.py +191 -0
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +138 -0
- rasa/shared/providers/embedding/openai_embedding_client.py +172 -0
- rasa/shared/providers/llm/__init__.py +0 -0
- rasa/shared/providers/llm/_base_litellm_client.py +265 -0
- rasa/shared/providers/llm/azure_openai_llm_client.py +415 -0
- rasa/shared/providers/llm/default_litellm_llm_client.py +110 -0
- rasa/shared/providers/llm/litellm_router_llm_client.py +202 -0
- rasa/shared/providers/llm/llm_client.py +78 -0
- rasa/shared/providers/llm/llm_response.py +50 -0
- rasa/shared/providers/llm/openai_llm_client.py +161 -0
- rasa/shared/providers/llm/rasa_llm_client.py +120 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +276 -0
- rasa/shared/providers/mappings.py +94 -0
- rasa/shared/providers/router/__init__.py +0 -0
- rasa/shared/providers/router/_base_litellm_router_client.py +185 -0
- rasa/shared/providers/router/router_client.py +75 -0
- rasa/shared/utils/__init__.py +0 -0
- rasa/shared/utils/cli.py +102 -0
- rasa/shared/utils/common.py +324 -0
- rasa/shared/utils/constants.py +4 -0
- rasa/shared/utils/health_check/__init__.py +0 -0
- rasa/shared/utils/health_check/embeddings_health_check_mixin.py +31 -0
- rasa/shared/utils/health_check/health_check.py +258 -0
- rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
- rasa/shared/utils/io.py +499 -0
- rasa/shared/utils/llm.py +764 -0
- rasa/shared/utils/pykwalify_extensions.py +27 -0
- rasa/shared/utils/schemas/__init__.py +0 -0
- rasa/shared/utils/schemas/config.yml +2 -0
- rasa/shared/utils/schemas/domain.yml +145 -0
- rasa/shared/utils/schemas/events.py +214 -0
- rasa/shared/utils/schemas/model_config.yml +36 -0
- rasa/shared/utils/schemas/stories.yml +173 -0
- rasa/shared/utils/yaml.py +1068 -0
- rasa/studio/__init__.py +0 -0
- rasa/studio/auth.py +270 -0
- rasa/studio/config.py +136 -0
- rasa/studio/constants.py +19 -0
- rasa/studio/data_handler.py +368 -0
- rasa/studio/download.py +489 -0
- rasa/studio/results_logger.py +137 -0
- rasa/studio/train.py +134 -0
- rasa/studio/upload.py +563 -0
- rasa/telemetry.py +1876 -0
- rasa/tracing/__init__.py +0 -0
- rasa/tracing/config.py +355 -0
- rasa/tracing/constants.py +62 -0
- rasa/tracing/instrumentation/__init__.py +0 -0
- rasa/tracing/instrumentation/attribute_extractors.py +765 -0
- rasa/tracing/instrumentation/instrumentation.py +1306 -0
- rasa/tracing/instrumentation/intentless_policy_instrumentation.py +144 -0
- rasa/tracing/instrumentation/metrics.py +294 -0
- rasa/tracing/metric_instrument_provider.py +205 -0
- rasa/utils/__init__.py +0 -0
- rasa/utils/beta.py +83 -0
- rasa/utils/cli.py +28 -0
- rasa/utils/common.py +639 -0
- rasa/utils/converter.py +53 -0
- rasa/utils/endpoints.py +331 -0
- rasa/utils/io.py +252 -0
- rasa/utils/json_utils.py +60 -0
- rasa/utils/licensing.py +542 -0
- rasa/utils/log_utils.py +181 -0
- rasa/utils/mapper.py +210 -0
- rasa/utils/ml_utils.py +147 -0
- rasa/utils/plotting.py +362 -0
- rasa/utils/sanic_error_handler.py +32 -0
- rasa/utils/singleton.py +23 -0
- rasa/utils/tensorflow/__init__.py +0 -0
- rasa/utils/tensorflow/callback.py +112 -0
- rasa/utils/tensorflow/constants.py +116 -0
- rasa/utils/tensorflow/crf.py +492 -0
- rasa/utils/tensorflow/data_generator.py +440 -0
- rasa/utils/tensorflow/environment.py +161 -0
- rasa/utils/tensorflow/exceptions.py +5 -0
- rasa/utils/tensorflow/feature_array.py +366 -0
- rasa/utils/tensorflow/layers.py +1565 -0
- rasa/utils/tensorflow/layers_utils.py +113 -0
- rasa/utils/tensorflow/metrics.py +281 -0
- rasa/utils/tensorflow/model_data.py +798 -0
- rasa/utils/tensorflow/model_data_utils.py +499 -0
- rasa/utils/tensorflow/models.py +935 -0
- rasa/utils/tensorflow/rasa_layers.py +1094 -0
- rasa/utils/tensorflow/transformer.py +640 -0
- rasa/utils/tensorflow/types.py +6 -0
- rasa/utils/train_utils.py +572 -0
- rasa/utils/url_tools.py +53 -0
- rasa/utils/yaml.py +54 -0
- rasa/validator.py +1644 -0
- rasa/version.py +3 -0
- rasa_pro-3.12.0.dev1.dist-info/METADATA +199 -0
- rasa_pro-3.12.0.dev1.dist-info/NOTICE +5 -0
- rasa_pro-3.12.0.dev1.dist-info/RECORD +790 -0
- rasa_pro-3.12.0.dev1.dist-info/WHEEL +4 -0
- rasa_pro-3.12.0.dev1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,2172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import collections
|
|
4
|
+
import copy
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from functools import cached_property
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Any,
|
|
13
|
+
Callable,
|
|
14
|
+
ClassVar,
|
|
15
|
+
Dict,
|
|
16
|
+
Iterable,
|
|
17
|
+
List,
|
|
18
|
+
MutableMapping,
|
|
19
|
+
NamedTuple,
|
|
20
|
+
NoReturn,
|
|
21
|
+
Optional,
|
|
22
|
+
Set,
|
|
23
|
+
Text,
|
|
24
|
+
Tuple,
|
|
25
|
+
Union,
|
|
26
|
+
cast,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
import structlog
|
|
30
|
+
from ruamel.yaml.scalarstring import DoubleQuotedScalarString
|
|
31
|
+
|
|
32
|
+
import rasa.shared.core.slot_mappings
|
|
33
|
+
import rasa.shared.utils.common
|
|
34
|
+
import rasa.shared.utils.io
|
|
35
|
+
from rasa.shared.constants import (
|
|
36
|
+
DEFAULT_CARRY_OVER_SLOTS_TO_NEW_SESSION,
|
|
37
|
+
DEFAULT_SESSION_EXPIRATION_TIME_IN_MINUTES,
|
|
38
|
+
DOCS_URL_DOMAINS,
|
|
39
|
+
DOCS_URL_FORMS,
|
|
40
|
+
DOCS_URL_RESPONSES,
|
|
41
|
+
DOMAIN_SCHEMA_FILE,
|
|
42
|
+
IGNORED_INTENTS,
|
|
43
|
+
LATEST_TRAINING_DATA_FORMAT_VERSION,
|
|
44
|
+
REQUIRED_SLOTS_KEY,
|
|
45
|
+
RESPONSE_CONDITION,
|
|
46
|
+
)
|
|
47
|
+
from rasa.shared.core.constants import (
|
|
48
|
+
ACTION_SHOULD_SEND_DOMAIN,
|
|
49
|
+
ACTIVE_LOOP,
|
|
50
|
+
KNOWLEDGE_BASE_SLOT_NAMES,
|
|
51
|
+
MAPPING_CONDITIONS,
|
|
52
|
+
MAPPING_TYPE,
|
|
53
|
+
SLOT_MAPPINGS,
|
|
54
|
+
SlotMappingType,
|
|
55
|
+
)
|
|
56
|
+
from rasa.shared.core.events import SlotSet, UserUttered
|
|
57
|
+
from rasa.shared.core.slots import (
|
|
58
|
+
AnySlot,
|
|
59
|
+
CategoricalSlot,
|
|
60
|
+
ListSlot,
|
|
61
|
+
Slot,
|
|
62
|
+
TextSlot,
|
|
63
|
+
)
|
|
64
|
+
from rasa.shared.exceptions import (
|
|
65
|
+
RasaException,
|
|
66
|
+
YamlException,
|
|
67
|
+
YamlSyntaxException,
|
|
68
|
+
)
|
|
69
|
+
from rasa.shared.nlu.constants import (
|
|
70
|
+
ENTITIES,
|
|
71
|
+
ENTITY_ATTRIBUTE_GROUP,
|
|
72
|
+
ENTITY_ATTRIBUTE_ROLE,
|
|
73
|
+
ENTITY_ATTRIBUTE_TYPE,
|
|
74
|
+
INTENT_NAME_KEY,
|
|
75
|
+
RESPONSE_IDENTIFIER_DELIMITER,
|
|
76
|
+
)
|
|
77
|
+
from rasa.shared.utils.cli import print_error_and_exit
|
|
78
|
+
from rasa.shared.utils.yaml import (
|
|
79
|
+
KEY_TRAINING_DATA_FORMAT_VERSION,
|
|
80
|
+
dump_obj_as_yaml_to_string,
|
|
81
|
+
read_yaml,
|
|
82
|
+
read_yaml_file,
|
|
83
|
+
validate_raw_yaml_using_schema_file_with_responses,
|
|
84
|
+
validate_training_data_format_version,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if TYPE_CHECKING:
|
|
88
|
+
from rasa.shared.core.trackers import DialogueStateTracker
|
|
89
|
+
|
|
90
|
+
CARRY_OVER_SLOTS_KEY = "carry_over_slots_to_new_session"
|
|
91
|
+
SESSION_EXPIRATION_TIME_KEY = "session_expiration_time"
|
|
92
|
+
SESSION_CONFIG_KEY = "session_config"
|
|
93
|
+
USED_ENTITIES_KEY = "used_entities"
|
|
94
|
+
USE_ENTITIES_KEY = "use_entities"
|
|
95
|
+
IGNORE_ENTITIES_KEY = "ignore_entities"
|
|
96
|
+
IS_RETRIEVAL_INTENT_KEY = "is_retrieval_intent"
|
|
97
|
+
ENTITY_ROLES_KEY = "roles"
|
|
98
|
+
ENTITY_GROUPS_KEY = "groups"
|
|
99
|
+
ENTITY_FEATURIZATION_KEY = "influence_conversation"
|
|
100
|
+
|
|
101
|
+
KEY_SLOTS = "slots"
|
|
102
|
+
KEY_INTENTS = "intents"
|
|
103
|
+
KEY_ENTITIES = "entities"
|
|
104
|
+
KEY_RESPONSES = "responses"
|
|
105
|
+
KEY_ACTIONS = "actions"
|
|
106
|
+
KEY_FORMS = "forms"
|
|
107
|
+
KEY_E2E_ACTIONS = "e2e_actions"
|
|
108
|
+
KEY_RESPONSES_TEXT = "text"
|
|
109
|
+
KEY_RESPONSES_IMAGE = "image"
|
|
110
|
+
KEY_RESPONSES_CUSTOM = "custom"
|
|
111
|
+
KEY_RESPONSES_BUTTONS = "buttons"
|
|
112
|
+
KEY_RESPONSES_ATTACHMENT = "attachment"
|
|
113
|
+
KEY_RESPONSES_QUICK_REPLIES = "quick_replies"
|
|
114
|
+
|
|
115
|
+
RESPONSE_KEYS_TO_INTERPOLATE = [
|
|
116
|
+
KEY_RESPONSES_TEXT,
|
|
117
|
+
KEY_RESPONSES_IMAGE,
|
|
118
|
+
KEY_RESPONSES_CUSTOM,
|
|
119
|
+
KEY_RESPONSES_BUTTONS,
|
|
120
|
+
KEY_RESPONSES_ATTACHMENT,
|
|
121
|
+
KEY_RESPONSES_QUICK_REPLIES,
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
ALL_DOMAIN_KEYS = [
|
|
125
|
+
KEY_SLOTS,
|
|
126
|
+
KEY_FORMS,
|
|
127
|
+
KEY_ACTIONS,
|
|
128
|
+
KEY_ENTITIES,
|
|
129
|
+
KEY_INTENTS,
|
|
130
|
+
KEY_RESPONSES,
|
|
131
|
+
KEY_E2E_ACTIONS,
|
|
132
|
+
SESSION_CONFIG_KEY,
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
PREV_PREFIX = "prev_"
|
|
136
|
+
|
|
137
|
+
# State is a dictionary with keys (USER, PREVIOUS_ACTION, SLOTS, ACTIVE_LOOP)
|
|
138
|
+
# representing the origin of a SubState;
|
|
139
|
+
# the values are SubStates, that contain the information needed for featurization
|
|
140
|
+
SubStateValue = Union[Text, Tuple[Union[float, Text], ...]]
|
|
141
|
+
SubState = MutableMapping[Text, SubStateValue]
|
|
142
|
+
State = Dict[Text, SubState]
|
|
143
|
+
|
|
144
|
+
structlogger = structlog.get_logger(__name__)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class InvalidDomain(RasaException):
|
|
148
|
+
"""Exception that can be raised when domain is not valid."""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class ActionNotFoundException(ValueError, RasaException):
|
|
152
|
+
"""Raised when an action name could not be found."""
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class SessionConfig(NamedTuple):
|
|
156
|
+
"""The Session Configuration."""
|
|
157
|
+
|
|
158
|
+
session_expiration_time: float # in minutes
|
|
159
|
+
carry_over_slots: bool
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def default() -> SessionConfig:
|
|
163
|
+
"""Returns the SessionConfig with the default values."""
|
|
164
|
+
return SessionConfig(
|
|
165
|
+
DEFAULT_SESSION_EXPIRATION_TIME_IN_MINUTES,
|
|
166
|
+
DEFAULT_CARRY_OVER_SLOTS_TO_NEW_SESSION,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def are_sessions_enabled(self) -> bool:
|
|
170
|
+
"""Returns a boolean value depending on the value of session_expiration_time."""
|
|
171
|
+
return self.session_expiration_time > 0
|
|
172
|
+
|
|
173
|
+
def as_dict(self) -> Dict:
|
|
174
|
+
"""Return serialized `SessionConfig`."""
|
|
175
|
+
return {
|
|
176
|
+
"session_expiration_time": self.session_expiration_time,
|
|
177
|
+
"carry_over_slots_to_new_session": self.carry_over_slots,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass
|
|
182
|
+
class EntityProperties:
|
|
183
|
+
"""Class for keeping track of the properties of entities in the domain."""
|
|
184
|
+
|
|
185
|
+
entities: List[Text]
|
|
186
|
+
roles: Dict[Text, List[Text]]
|
|
187
|
+
groups: Dict[Text, List[Text]]
|
|
188
|
+
default_ignored_entities: List[Text]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class Domain:
|
|
192
|
+
"""The domain specifies the universe in which the bot's policy acts.
|
|
193
|
+
|
|
194
|
+
A Domain subclass provides the actions the bot can take, the intents
|
|
195
|
+
and entities it can recognise.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
validate_yaml: ClassVar[bool] = True
|
|
199
|
+
expand_env_vars: ClassVar[bool] = True
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def empty(cls) -> Domain:
|
|
203
|
+
"""Returns empty Domain."""
|
|
204
|
+
return Domain.from_dict({})
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def load(cls, paths: Union[List[Union[Path, Text]], Text, Path]) -> Domain:
|
|
208
|
+
"""Returns loaded Domain after merging all domain files."""
|
|
209
|
+
if not paths:
|
|
210
|
+
raise InvalidDomain(
|
|
211
|
+
"No domain file was specified. Please specify a path "
|
|
212
|
+
"to a valid domain file."
|
|
213
|
+
)
|
|
214
|
+
elif not isinstance(paths, list) and not isinstance(paths, set):
|
|
215
|
+
paths = [paths]
|
|
216
|
+
|
|
217
|
+
domain = Domain.empty()
|
|
218
|
+
for path in paths:
|
|
219
|
+
other = cls.from_path(path)
|
|
220
|
+
domain = domain.merge(other)
|
|
221
|
+
|
|
222
|
+
return domain
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def from_path(cls, path: Union[Text, Path]) -> Domain:
|
|
226
|
+
"""Loads the `Domain` from a path.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
path: Path to the domain file.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
The instantiated `Domain` object.
|
|
233
|
+
"""
|
|
234
|
+
path = os.path.abspath(path)
|
|
235
|
+
|
|
236
|
+
if os.path.isfile(path):
|
|
237
|
+
domain = cls.from_file(path)
|
|
238
|
+
elif os.path.isdir(path):
|
|
239
|
+
domain = cls.from_directory(path)
|
|
240
|
+
else:
|
|
241
|
+
raise InvalidDomain(
|
|
242
|
+
"Failed to load domain specification from '{}'. "
|
|
243
|
+
"File not found!".format(os.path.abspath(path))
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return domain
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def from_file(cls, path: Text) -> Domain:
|
|
250
|
+
"""Loads the `Domain` from a YAML file.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
path: Path to the domain file.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
The instantiated `Domain` object.
|
|
257
|
+
"""
|
|
258
|
+
return cls.from_yaml(rasa.shared.utils.io.read_file(path), path)
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def from_yaml(cls, yaml: Text, original_filename: Text = "") -> Domain:
|
|
262
|
+
"""Loads the `Domain` from YAML text after validating it.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
yaml: The YAML string to load the domain from.
|
|
266
|
+
original_filename: The filename of the original YAML file.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
The instantiated `Domain` object.
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
data = cls._dict_from_raw_yaml_content(yaml)
|
|
273
|
+
if not validate_training_data_format_version(data, original_filename):
|
|
274
|
+
return Domain.empty()
|
|
275
|
+
return cls.from_dict(data)
|
|
276
|
+
except YamlException as e:
|
|
277
|
+
e.filename = original_filename
|
|
278
|
+
raise e
|
|
279
|
+
|
|
280
|
+
@classmethod
|
|
281
|
+
def from_dict(cls, data: Dict) -> Domain:
|
|
282
|
+
"""Deserializes and creates domain.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
data: The serialized domain.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
The instantiated `Domain` object.
|
|
289
|
+
"""
|
|
290
|
+
responses = data.get(KEY_RESPONSES, {})
|
|
291
|
+
|
|
292
|
+
domain_slots = data.get(KEY_SLOTS, {})
|
|
293
|
+
if domain_slots:
|
|
294
|
+
rasa.shared.core.slot_mappings.validate_slot_mappings(domain_slots)
|
|
295
|
+
slots = cls.collect_slots(domain_slots)
|
|
296
|
+
domain_actions = data.get(KEY_ACTIONS, [])
|
|
297
|
+
actions = cls._collect_action_names(domain_actions)
|
|
298
|
+
|
|
299
|
+
additional_arguments = {
|
|
300
|
+
**data.get("config", {}),
|
|
301
|
+
"actions_which_explicitly_need_domain": (
|
|
302
|
+
cls._collect_actions_which_explicitly_need_domain(domain_actions)
|
|
303
|
+
),
|
|
304
|
+
}
|
|
305
|
+
session_config = cls._get_session_config(data.get(SESSION_CONFIG_KEY, {}))
|
|
306
|
+
intents = data.get(KEY_INTENTS, {})
|
|
307
|
+
|
|
308
|
+
forms = data.get(KEY_FORMS, {})
|
|
309
|
+
_validate_forms(forms)
|
|
310
|
+
|
|
311
|
+
return cls(
|
|
312
|
+
intents=intents or [],
|
|
313
|
+
entities=data.get(KEY_ENTITIES, []),
|
|
314
|
+
slots=slots,
|
|
315
|
+
responses=responses,
|
|
316
|
+
action_names=actions,
|
|
317
|
+
forms=data.get(KEY_FORMS, {}),
|
|
318
|
+
data=Domain._cleaned_data(data),
|
|
319
|
+
action_texts=data.get(KEY_E2E_ACTIONS, []),
|
|
320
|
+
session_config=session_config,
|
|
321
|
+
**additional_arguments,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def _get_session_config(session_config: Dict) -> SessionConfig:
|
|
326
|
+
session_expiration_time_min = session_config.get(SESSION_EXPIRATION_TIME_KEY)
|
|
327
|
+
|
|
328
|
+
if session_expiration_time_min is None:
|
|
329
|
+
session_expiration_time_min = DEFAULT_SESSION_EXPIRATION_TIME_IN_MINUTES
|
|
330
|
+
|
|
331
|
+
carry_over_slots = session_config.get(
|
|
332
|
+
CARRY_OVER_SLOTS_KEY, DEFAULT_CARRY_OVER_SLOTS_TO_NEW_SESSION
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return SessionConfig(session_expiration_time_min, carry_over_slots)
|
|
336
|
+
|
|
337
|
+
@classmethod
|
|
338
|
+
def from_directory(cls, path: Text) -> Domain:
|
|
339
|
+
"""Loads and merges multiple domain files recursively from a directory tree.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
path: Path to the root directory.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
The instantiated `Domain` object.
|
|
346
|
+
"""
|
|
347
|
+
combined: Dict[Text, Any] = {}
|
|
348
|
+
duplicates: List[Dict[Text, List[Text]]] = []
|
|
349
|
+
|
|
350
|
+
for root, _, files in os.walk(path, followlinks=True):
|
|
351
|
+
for file in files:
|
|
352
|
+
full_path = os.path.join(root, file)
|
|
353
|
+
if not Domain.is_domain_file(full_path):
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
other_dict = cls._dict_from_raw_yaml_content(
|
|
357
|
+
rasa.shared.utils.io.read_file(full_path)
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
combined = Domain.merge_domain_dicts(other_dict, combined)
|
|
361
|
+
duplicates.append(combined.pop("duplicates", {}))
|
|
362
|
+
|
|
363
|
+
Domain._handle_duplicates_from_multiple_files(duplicates)
|
|
364
|
+
|
|
365
|
+
domain = Domain.from_dict(combined)
|
|
366
|
+
return domain
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def _handle_duplicates_from_multiple_files(
|
|
370
|
+
duplicates_from_multiple_files: List[Dict[Text, List[Text]]],
|
|
371
|
+
) -> None:
|
|
372
|
+
combined_duplicates: Dict[Text, List[Text]] = collections.defaultdict(list)
|
|
373
|
+
|
|
374
|
+
for duplicates in duplicates_from_multiple_files:
|
|
375
|
+
duplicates = rasa.shared.utils.common.clean_duplicates(duplicates)
|
|
376
|
+
|
|
377
|
+
for key in duplicates.keys():
|
|
378
|
+
combined_duplicates[key].extend(duplicates[key])
|
|
379
|
+
|
|
380
|
+
# handle duplicated responses by raising an error
|
|
381
|
+
duplicated_responses = combined_duplicates.pop(KEY_RESPONSES, [])
|
|
382
|
+
Domain._handle_duplicate_responses(duplicated_responses)
|
|
383
|
+
|
|
384
|
+
# warn about other duplicates
|
|
385
|
+
warn_about_duplicates_found_during_domain_merging(combined_duplicates)
|
|
386
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
def _handle_duplicate_responses(response_duplicates: List[Text]) -> None:
|
|
389
|
+
if response_duplicates:
|
|
390
|
+
for response in response_duplicates:
|
|
391
|
+
structlogger.error(
|
|
392
|
+
"domain.duplicate_response",
|
|
393
|
+
response=response,
|
|
394
|
+
event_info=(
|
|
395
|
+
f"Response '{response}' is defined in multiple domains. "
|
|
396
|
+
f"Please make sure this response is only defined in one domain."
|
|
397
|
+
),
|
|
398
|
+
)
|
|
399
|
+
print_error_and_exit(
|
|
400
|
+
"Unable to merge domains due to duplicate responses in domain."
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def merge(
|
|
404
|
+
self,
|
|
405
|
+
domain: Optional[Domain],
|
|
406
|
+
override: bool = False,
|
|
407
|
+
ignore_warnings_about_duplicates: bool = False,
|
|
408
|
+
) -> Domain:
|
|
409
|
+
"""Merges this domain dict with another one, combining their attributes.
|
|
410
|
+
|
|
411
|
+
This method merges domain dicts, and ensures all attributes (like ``intents``,
|
|
412
|
+
``entities``, and ``actions``) are known to the Domain when the
|
|
413
|
+
object is created.
|
|
414
|
+
|
|
415
|
+
List attributes like ``intents`` and ``actions`` are deduped
|
|
416
|
+
and merged. Single attributes are taken from `domain1` unless
|
|
417
|
+
override is `True`, in which case they are taken from `domain2`.
|
|
418
|
+
"""
|
|
419
|
+
if not domain or domain.is_empty():
|
|
420
|
+
return self
|
|
421
|
+
|
|
422
|
+
if self.is_empty():
|
|
423
|
+
return domain
|
|
424
|
+
|
|
425
|
+
combined = self.__class__.merge_domain_dicts(
|
|
426
|
+
domain.as_dict(), self.as_dict(), override
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
duplicates = combined.pop("duplicates", {})
|
|
430
|
+
|
|
431
|
+
if not ignore_warnings_about_duplicates:
|
|
432
|
+
warn_about_duplicates_found_during_domain_merging(duplicates)
|
|
433
|
+
|
|
434
|
+
return Domain.from_dict(combined)
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def merge_domain_dicts(
|
|
438
|
+
domain_dict: Dict,
|
|
439
|
+
combined: Dict,
|
|
440
|
+
override: bool = False,
|
|
441
|
+
) -> Dict:
|
|
442
|
+
"""Combines two domain dictionaries."""
|
|
443
|
+
if not domain_dict:
|
|
444
|
+
return combined
|
|
445
|
+
|
|
446
|
+
if not combined:
|
|
447
|
+
return domain_dict
|
|
448
|
+
|
|
449
|
+
if override:
|
|
450
|
+
config = domain_dict.get("config", {})
|
|
451
|
+
for key, val in config.items():
|
|
452
|
+
combined["config"][key] = val
|
|
453
|
+
|
|
454
|
+
if (
|
|
455
|
+
override
|
|
456
|
+
or combined.get(SESSION_CONFIG_KEY) == SessionConfig.default().as_dict()
|
|
457
|
+
or combined.get(SESSION_CONFIG_KEY) is None
|
|
458
|
+
) and domain_dict.get(SESSION_CONFIG_KEY) not in [
|
|
459
|
+
None,
|
|
460
|
+
SessionConfig.default().as_dict(),
|
|
461
|
+
]:
|
|
462
|
+
combined[SESSION_CONFIG_KEY] = domain_dict[SESSION_CONFIG_KEY]
|
|
463
|
+
|
|
464
|
+
# remove existing forms from new actions
|
|
465
|
+
for form in combined.get(KEY_FORMS, []):
|
|
466
|
+
if form in domain_dict.get(KEY_ACTIONS, []):
|
|
467
|
+
domain_dict[KEY_ACTIONS].remove(form)
|
|
468
|
+
|
|
469
|
+
duplicates: Dict[Text, List[Text]] = {}
|
|
470
|
+
|
|
471
|
+
merge_func_mappings: Dict[Text, Callable[..., Any]] = {
|
|
472
|
+
KEY_INTENTS: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
473
|
+
KEY_ENTITIES: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
474
|
+
KEY_ACTIONS: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
475
|
+
KEY_E2E_ACTIONS: rasa.shared.utils.common.merge_lists,
|
|
476
|
+
KEY_FORMS: rasa.shared.utils.common.merge_dicts,
|
|
477
|
+
KEY_RESPONSES: rasa.shared.utils.common.merge_dicts,
|
|
478
|
+
KEY_SLOTS: rasa.shared.utils.common.merge_dicts,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
for key, merge_func in merge_func_mappings.items():
|
|
482
|
+
duplicates[key] = rasa.shared.utils.common.extract_duplicates(
|
|
483
|
+
combined.get(key, []), domain_dict.get(key, [])
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
default: Union[List[Any], Dict[Text, Any]] = (
|
|
487
|
+
{} if merge_func == rasa.shared.utils.common.merge_dicts else []
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
combined[key] = merge_func(
|
|
491
|
+
combined.get(key, default), domain_dict.get(key, default), override
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
if duplicates:
|
|
495
|
+
combined.update({"duplicates": duplicates})
|
|
496
|
+
|
|
497
|
+
return combined
|
|
498
|
+
|
|
499
|
+
def _preprocess_domain_dict(
|
|
500
|
+
self,
|
|
501
|
+
data: Dict,
|
|
502
|
+
store_entities_as_slots: bool,
|
|
503
|
+
session_config: SessionConfig,
|
|
504
|
+
) -> Dict:
|
|
505
|
+
data = self._add_default_keys_to_domain_dict(
|
|
506
|
+
data,
|
|
507
|
+
store_entities_as_slots,
|
|
508
|
+
session_config,
|
|
509
|
+
)
|
|
510
|
+
data = self._sanitize_intents_in_domain_dict(data)
|
|
511
|
+
|
|
512
|
+
return data
|
|
513
|
+
|
|
514
|
+
@staticmethod
|
|
515
|
+
def _add_default_keys_to_domain_dict(
|
|
516
|
+
data: Dict,
|
|
517
|
+
store_entities_as_slots: bool,
|
|
518
|
+
session_config: SessionConfig,
|
|
519
|
+
) -> Dict:
|
|
520
|
+
# add the config, session_config and training data version defaults
|
|
521
|
+
# if not included in the original domain dict
|
|
522
|
+
if "config" not in data and not store_entities_as_slots:
|
|
523
|
+
data.update(
|
|
524
|
+
{"config": {"store_entities_as_slots": store_entities_as_slots}}
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
if SESSION_CONFIG_KEY not in data:
|
|
528
|
+
data.update(
|
|
529
|
+
{
|
|
530
|
+
SESSION_CONFIG_KEY: {
|
|
531
|
+
SESSION_EXPIRATION_TIME_KEY: (
|
|
532
|
+
session_config.session_expiration_time
|
|
533
|
+
),
|
|
534
|
+
CARRY_OVER_SLOTS_KEY: session_config.carry_over_slots,
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
if KEY_TRAINING_DATA_FORMAT_VERSION not in data:
|
|
540
|
+
data.update(
|
|
541
|
+
{
|
|
542
|
+
KEY_TRAINING_DATA_FORMAT_VERSION: DoubleQuotedScalarString(
|
|
543
|
+
LATEST_TRAINING_DATA_FORMAT_VERSION
|
|
544
|
+
)
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
return data
|
|
549
|
+
|
|
550
|
+
@staticmethod
|
|
551
|
+
def _reset_intent_flags(intent: Dict[Text, Any]) -> None:
|
|
552
|
+
for intent_property in intent.values():
|
|
553
|
+
if (
|
|
554
|
+
USE_ENTITIES_KEY in intent_property.keys()
|
|
555
|
+
and not intent_property[USE_ENTITIES_KEY]
|
|
556
|
+
):
|
|
557
|
+
intent_property[USE_ENTITIES_KEY] = []
|
|
558
|
+
if (
|
|
559
|
+
IGNORE_ENTITIES_KEY in intent_property.keys()
|
|
560
|
+
and not intent_property[IGNORE_ENTITIES_KEY]
|
|
561
|
+
):
|
|
562
|
+
intent_property[IGNORE_ENTITIES_KEY] = []
|
|
563
|
+
|
|
564
|
+
@staticmethod
|
|
565
|
+
def _sanitize_intents_in_domain_dict(data: Dict[Text, Any]) -> Dict[Text, Any]:
|
|
566
|
+
if not data.get(KEY_INTENTS):
|
|
567
|
+
return data
|
|
568
|
+
|
|
569
|
+
for intent in data.get(KEY_INTENTS, []):
|
|
570
|
+
if isinstance(intent, dict):
|
|
571
|
+
Domain._reset_intent_flags(intent)
|
|
572
|
+
|
|
573
|
+
data[KEY_INTENTS] = Domain._sort_intent_names_alphabetical_order(
|
|
574
|
+
intents=data.get(KEY_INTENTS)
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
return data
|
|
578
|
+
|
|
579
|
+
@staticmethod
|
|
580
|
+
def collect_slots(slot_dict: Dict[Text, Any]) -> List[Slot]:
|
|
581
|
+
"""Collects a list of slots from a dictionary."""
|
|
582
|
+
slots = []
|
|
583
|
+
# make a copy to not alter the input dictionary
|
|
584
|
+
slot_dict = copy.deepcopy(slot_dict)
|
|
585
|
+
# Don't sort the slots, see https://github.com/RasaHQ/rasa-x/issues/3900
|
|
586
|
+
for slot_name in slot_dict:
|
|
587
|
+
slot_type = slot_dict[slot_name].pop("type", None)
|
|
588
|
+
slot_class = Slot.resolve_by_type(slot_type)
|
|
589
|
+
|
|
590
|
+
if SLOT_MAPPINGS not in slot_dict[slot_name]:
|
|
591
|
+
structlogger.debug(
|
|
592
|
+
"domain.collect_slots.no_mappings_defined",
|
|
593
|
+
event_info=(
|
|
594
|
+
f"Slot '{slot_name}' has no mappings defined. "
|
|
595
|
+
f"Assigning the default FROM_LLM slot mapping."
|
|
596
|
+
),
|
|
597
|
+
)
|
|
598
|
+
slot_dict[slot_name][SLOT_MAPPINGS] = [
|
|
599
|
+
{MAPPING_TYPE: SlotMappingType.FROM_LLM.value}
|
|
600
|
+
]
|
|
601
|
+
|
|
602
|
+
slot = slot_class(slot_name, **slot_dict[slot_name])
|
|
603
|
+
slots.append(slot)
|
|
604
|
+
return slots
|
|
605
|
+
|
|
606
|
+
@staticmethod
|
|
607
|
+
def _transform_intent_properties_for_internal_use(
|
|
608
|
+
intent: Dict[Text, Any], entity_properties: EntityProperties
|
|
609
|
+
) -> Dict[Text, Any]:
|
|
610
|
+
"""Transforms the intent's parameters in a format suitable for internal use.
|
|
611
|
+
|
|
612
|
+
When an intent is retrieved from the `domain.yml` file, it contains two
|
|
613
|
+
parameters, the `use_entities` and the `ignore_entities` parameter.
|
|
614
|
+
With the values of these two parameters the Domain class is updated, a new
|
|
615
|
+
parameter is added to the intent called `used_entities` and the two
|
|
616
|
+
previous parameters are deleted. This happens because internally only the
|
|
617
|
+
parameter `used_entities` is needed to list all the entities that should be
|
|
618
|
+
used for this intent.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
intent: The intent as retrieved from the `domain.yml` file thus having two
|
|
622
|
+
parameters, the `use_entities` and the `ignore_entities` parameter.
|
|
623
|
+
entity_properties: Entity properties as provided by the domain file.
|
|
624
|
+
|
|
625
|
+
Returns:
|
|
626
|
+
The intent with the new format thus having only one parameter called
|
|
627
|
+
`used_entities` since this is the expected format of the intent
|
|
628
|
+
when used internally.
|
|
629
|
+
"""
|
|
630
|
+
name, properties = next(iter(intent.items()))
|
|
631
|
+
|
|
632
|
+
if properties:
|
|
633
|
+
properties.setdefault(USE_ENTITIES_KEY, True)
|
|
634
|
+
else:
|
|
635
|
+
raise InvalidDomain(
|
|
636
|
+
f"In the `domain.yml` file, the intent '{name}' cannot have value of"
|
|
637
|
+
f" `{type(properties)}`. If you have placed a ':' character after the"
|
|
638
|
+
f" intent's name without adding any additional parameters to this"
|
|
639
|
+
f" intent then you would need to remove the ':' character. Please see"
|
|
640
|
+
f" {rasa.shared.constants.DOCS_URL_DOMAINS} for more information on how"
|
|
641
|
+
f" to correctly add `intents` in the `domain` and"
|
|
642
|
+
f" {rasa.shared.constants.DOCS_URL_INTENTS} for examples on"
|
|
643
|
+
f" when to use the ':' character after an intent's name."
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
properties.setdefault(
|
|
647
|
+
IGNORE_ENTITIES_KEY, entity_properties.default_ignored_entities
|
|
648
|
+
)
|
|
649
|
+
if not properties[USE_ENTITIES_KEY]: # this covers False, None and []
|
|
650
|
+
properties[USE_ENTITIES_KEY] = []
|
|
651
|
+
|
|
652
|
+
# `use_entities` is either a list of explicitly included entities
|
|
653
|
+
# or `True` if all should be included
|
|
654
|
+
# if the listed entities have a role or group label, concatenate the entity
|
|
655
|
+
# label with the corresponding role or group label to make sure roles and
|
|
656
|
+
# groups can also influence the dialogue predictions
|
|
657
|
+
if properties[USE_ENTITIES_KEY] is True:
|
|
658
|
+
included_entities = set(entity_properties.entities) - set(
|
|
659
|
+
entity_properties.default_ignored_entities
|
|
660
|
+
)
|
|
661
|
+
included_entities.update(
|
|
662
|
+
Domain.concatenate_entity_labels(entity_properties.roles)
|
|
663
|
+
)
|
|
664
|
+
included_entities.update(
|
|
665
|
+
Domain.concatenate_entity_labels(entity_properties.groups)
|
|
666
|
+
)
|
|
667
|
+
else:
|
|
668
|
+
included_entities = set(properties[USE_ENTITIES_KEY])
|
|
669
|
+
for entity in list(included_entities):
|
|
670
|
+
included_entities.update(
|
|
671
|
+
Domain.concatenate_entity_labels(entity_properties.roles, entity)
|
|
672
|
+
)
|
|
673
|
+
included_entities.update(
|
|
674
|
+
Domain.concatenate_entity_labels(entity_properties.groups, entity)
|
|
675
|
+
)
|
|
676
|
+
excluded_entities = set(properties[IGNORE_ENTITIES_KEY])
|
|
677
|
+
for entity in list(excluded_entities):
|
|
678
|
+
excluded_entities.update(
|
|
679
|
+
Domain.concatenate_entity_labels(entity_properties.roles, entity)
|
|
680
|
+
)
|
|
681
|
+
excluded_entities.update(
|
|
682
|
+
Domain.concatenate_entity_labels(entity_properties.groups, entity)
|
|
683
|
+
)
|
|
684
|
+
used_entities = list(included_entities - excluded_entities)
|
|
685
|
+
used_entities.sort()
|
|
686
|
+
|
|
687
|
+
# Only print warning for ambiguous configurations if entities were included
|
|
688
|
+
# explicitly.
|
|
689
|
+
explicitly_included = isinstance(properties[USE_ENTITIES_KEY], list)
|
|
690
|
+
ambiguous_entities = included_entities.intersection(excluded_entities)
|
|
691
|
+
if explicitly_included and ambiguous_entities:
|
|
692
|
+
structlogger.warning(
|
|
693
|
+
"domain.ambiguous_entities",
|
|
694
|
+
intent=name,
|
|
695
|
+
entities=ambiguous_entities,
|
|
696
|
+
event_info=(
|
|
697
|
+
f"Entities: '{ambiguous_entities}' are "
|
|
698
|
+
f"explicitly included and excluded for "
|
|
699
|
+
f"intent '{name}'. Excluding takes precedence "
|
|
700
|
+
f"in this case. Please resolve that ambiguity."
|
|
701
|
+
),
|
|
702
|
+
docs=f"{DOCS_URL_DOMAINS}",
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
properties[USED_ENTITIES_KEY] = used_entities
|
|
706
|
+
del properties[USE_ENTITIES_KEY]
|
|
707
|
+
del properties[IGNORE_ENTITIES_KEY]
|
|
708
|
+
|
|
709
|
+
return intent
|
|
710
|
+
|
|
711
|
+
@cached_property
|
|
712
|
+
def retrieval_intents(self) -> List[Text]:
|
|
713
|
+
"""List retrieval intents present in the domain."""
|
|
714
|
+
return [
|
|
715
|
+
intent
|
|
716
|
+
for intent in self.intent_properties
|
|
717
|
+
if self.intent_properties[intent].get(IS_RETRIEVAL_INTENT_KEY)
|
|
718
|
+
]
|
|
719
|
+
|
|
720
|
+
@classmethod
|
|
721
|
+
def collect_entity_properties(
|
|
722
|
+
cls, domain_entities: List[Union[Text, Dict[Text, Any]]]
|
|
723
|
+
) -> EntityProperties:
|
|
724
|
+
"""Get entity properties for a domain from what is provided by a domain file.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
domain_entities: The entities as provided by a domain file.
|
|
728
|
+
|
|
729
|
+
Returns:
|
|
730
|
+
An instance of EntityProperties.
|
|
731
|
+
"""
|
|
732
|
+
entity_properties = EntityProperties([], {}, {}, [])
|
|
733
|
+
for entity in domain_entities:
|
|
734
|
+
if isinstance(entity, str):
|
|
735
|
+
entity_properties.entities.append(entity)
|
|
736
|
+
elif isinstance(entity, dict):
|
|
737
|
+
for _entity, sub_labels in entity.items():
|
|
738
|
+
entity_properties.entities.append(_entity)
|
|
739
|
+
if sub_labels:
|
|
740
|
+
if ENTITY_ROLES_KEY in sub_labels:
|
|
741
|
+
entity_properties.roles[_entity] = sub_labels[
|
|
742
|
+
ENTITY_ROLES_KEY
|
|
743
|
+
]
|
|
744
|
+
if ENTITY_GROUPS_KEY in sub_labels:
|
|
745
|
+
entity_properties.groups[_entity] = sub_labels[
|
|
746
|
+
ENTITY_GROUPS_KEY
|
|
747
|
+
]
|
|
748
|
+
if (
|
|
749
|
+
ENTITY_FEATURIZATION_KEY in sub_labels
|
|
750
|
+
and sub_labels[ENTITY_FEATURIZATION_KEY] is False
|
|
751
|
+
):
|
|
752
|
+
entity_properties.default_ignored_entities.append(_entity)
|
|
753
|
+
else:
|
|
754
|
+
raise InvalidDomain(
|
|
755
|
+
f"In the `domain.yml` file, the entity '{_entity}' cannot"
|
|
756
|
+
f" have value of `{type(sub_labels)}`. If you have placed a"
|
|
757
|
+
f" ':' character after the entity `{_entity}` without"
|
|
758
|
+
f" adding any additional parameters to this entity then you"
|
|
759
|
+
f" would need to remove the ':' character. Please see"
|
|
760
|
+
f" {rasa.shared.constants.DOCS_URL_DOMAINS} for more"
|
|
761
|
+
f" information on how to correctly add `entities` in the"
|
|
762
|
+
f" `domain` and {rasa.shared.constants.DOCS_URL_ENTITIES}"
|
|
763
|
+
f" for examples on when to use the ':' character after an"
|
|
764
|
+
f" entity's name."
|
|
765
|
+
)
|
|
766
|
+
else:
|
|
767
|
+
raise InvalidDomain(
|
|
768
|
+
f"Invalid domain. Entity is invalid, type of entity '{entity}' "
|
|
769
|
+
f"not supported: '{type(entity).__name__}'"
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
return entity_properties
|
|
773
|
+
|
|
774
|
+
@classmethod
|
|
775
|
+
def collect_intent_properties(
|
|
776
|
+
cls,
|
|
777
|
+
intents: List[Union[Text, Dict[Text, Any]]],
|
|
778
|
+
entity_properties: EntityProperties,
|
|
779
|
+
) -> Dict[Text, Dict[Text, Union[bool, List]]]:
|
|
780
|
+
"""Get intent properties for a domain from what is provided by a domain file.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
intents: The intents as provided by a domain file.
|
|
784
|
+
entity_properties: Entity properties as provided by the domain file.
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
The intent properties to be stored in the domain.
|
|
788
|
+
"""
|
|
789
|
+
# make a copy to not alter the input argument
|
|
790
|
+
intents = copy.deepcopy(intents)
|
|
791
|
+
intent_properties: Dict[Text, Any] = {}
|
|
792
|
+
duplicates = set()
|
|
793
|
+
|
|
794
|
+
for intent in intents:
|
|
795
|
+
intent_name, properties = cls._intent_properties(intent, entity_properties)
|
|
796
|
+
|
|
797
|
+
if intent_name in intent_properties.keys():
|
|
798
|
+
duplicates.add(intent_name)
|
|
799
|
+
|
|
800
|
+
intent_properties.update(properties)
|
|
801
|
+
|
|
802
|
+
if duplicates:
|
|
803
|
+
raise InvalidDomain(
|
|
804
|
+
f"Intents are not unique! Found multiple intents "
|
|
805
|
+
f"with name(s) {sorted(duplicates)}. "
|
|
806
|
+
f"Either rename or remove the duplicate ones."
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
cls._add_default_intents(intent_properties, entity_properties)
|
|
810
|
+
|
|
811
|
+
return intent_properties
|
|
812
|
+
|
|
813
|
+
@classmethod
|
|
814
|
+
def _intent_properties(
|
|
815
|
+
cls, intent: Union[Text, Dict[Text, Any]], entity_properties: EntityProperties
|
|
816
|
+
) -> Tuple[Text, Dict[Text, Any]]:
|
|
817
|
+
if not isinstance(intent, dict):
|
|
818
|
+
intent_name = intent
|
|
819
|
+
intent = {
|
|
820
|
+
intent_name: {
|
|
821
|
+
USE_ENTITIES_KEY: True,
|
|
822
|
+
IGNORE_ENTITIES_KEY: entity_properties.default_ignored_entities,
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
else:
|
|
826
|
+
intent_name = next(iter(intent.keys()))
|
|
827
|
+
try:
|
|
828
|
+
return (
|
|
829
|
+
intent_name,
|
|
830
|
+
cls._transform_intent_properties_for_internal_use(
|
|
831
|
+
intent, entity_properties
|
|
832
|
+
),
|
|
833
|
+
)
|
|
834
|
+
except AttributeError:
|
|
835
|
+
raise InvalidDomain(
|
|
836
|
+
f"Detected invalid intent definition: {intent}. "
|
|
837
|
+
f"Please make sure all intent definitions are valid."
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
@classmethod
|
|
841
|
+
def _add_default_intents(
|
|
842
|
+
cls,
|
|
843
|
+
intent_properties: Dict[Text, Dict[Text, Union[bool, List]]],
|
|
844
|
+
entity_properties: EntityProperties,
|
|
845
|
+
) -> None:
|
|
846
|
+
for intent_name in rasa.shared.core.constants.DEFAULT_INTENTS:
|
|
847
|
+
if intent_name not in intent_properties:
|
|
848
|
+
_, properties = cls._intent_properties(intent_name, entity_properties)
|
|
849
|
+
intent_properties.update(properties)
|
|
850
|
+
|
|
851
|
+
def __init__(
|
|
852
|
+
self,
|
|
853
|
+
intents: Union[Set[Text], List[Text], List[Dict[Text, Any]]],
|
|
854
|
+
entities: List[Union[Text, Dict[Text, Any]]],
|
|
855
|
+
slots: List[Slot],
|
|
856
|
+
responses: Dict[Text, List[Dict[Text, Any]]],
|
|
857
|
+
action_names: List[Text],
|
|
858
|
+
forms: Union[Dict[Text, Any], List[Text]],
|
|
859
|
+
data: Dict,
|
|
860
|
+
action_texts: Optional[List[Text]] = None,
|
|
861
|
+
store_entities_as_slots: bool = True,
|
|
862
|
+
session_config: SessionConfig = SessionConfig.default(),
|
|
863
|
+
**kwargs: Any,
|
|
864
|
+
) -> None:
|
|
865
|
+
"""Create a `Domain`.
|
|
866
|
+
|
|
867
|
+
Args:
|
|
868
|
+
intents: Intent labels.
|
|
869
|
+
entities: The names of entities which might be present in user messages.
|
|
870
|
+
slots: Slots to store information during the conversation.
|
|
871
|
+
responses: Bot responses. If an action with the same name is executed, it
|
|
872
|
+
will send the matching response to the user.
|
|
873
|
+
action_names: Names of custom actions.
|
|
874
|
+
forms: Form names and their slot mappings.
|
|
875
|
+
data: original domain dict representation.
|
|
876
|
+
action_texts: End-to-End bot utterances from end-to-end stories.
|
|
877
|
+
store_entities_as_slots: If `True` Rasa will automatically create `SlotSet`
|
|
878
|
+
events for entities if there are slots with the same name as the entity.
|
|
879
|
+
session_config: Configuration for conversation sessions. Conversations are
|
|
880
|
+
restarted at the end of a session.
|
|
881
|
+
kwargs: Additional arguments.
|
|
882
|
+
"""
|
|
883
|
+
self.entity_properties = self.collect_entity_properties(entities)
|
|
884
|
+
self.intent_properties = self.collect_intent_properties(
|
|
885
|
+
intents, self.entity_properties
|
|
886
|
+
)
|
|
887
|
+
self.overridden_default_intents = self._collect_overridden_default_intents(
|
|
888
|
+
intents
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
self.form_names, self.forms, overridden_form_actions = self._initialize_forms(
|
|
892
|
+
forms
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
action_names += overridden_form_actions
|
|
896
|
+
|
|
897
|
+
self.responses = responses
|
|
898
|
+
|
|
899
|
+
self.action_texts = action_texts if action_texts is not None else []
|
|
900
|
+
|
|
901
|
+
data_copy = copy.deepcopy(data)
|
|
902
|
+
self._data = self._preprocess_domain_dict(
|
|
903
|
+
data_copy,
|
|
904
|
+
store_entities_as_slots,
|
|
905
|
+
session_config,
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
self.session_config = session_config
|
|
909
|
+
|
|
910
|
+
self._custom_actions = action_names
|
|
911
|
+
self._actions_which_explicitly_need_domain = (
|
|
912
|
+
kwargs.get("actions_which_explicitly_need_domain") or []
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
# only includes custom actions and utterance actions
|
|
916
|
+
self.user_actions = self._combine_with_responses(action_names, responses)
|
|
917
|
+
|
|
918
|
+
# includes all action names (custom, utterance, default actions and forms)
|
|
919
|
+
# and action texts from end-to-end bot utterances
|
|
920
|
+
self.action_names_or_texts = (
|
|
921
|
+
self._combine_user_with_default_actions(self.user_actions)
|
|
922
|
+
+ [
|
|
923
|
+
form_name
|
|
924
|
+
for form_name in self.form_names
|
|
925
|
+
if form_name not in self._custom_actions
|
|
926
|
+
]
|
|
927
|
+
+ self.action_texts
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
self._user_slots = copy.copy(slots)
|
|
931
|
+
self.slots = slots
|
|
932
|
+
self._add_default_slots()
|
|
933
|
+
self.store_entities_as_slots = store_entities_as_slots
|
|
934
|
+
self._check_domain_sanity()
|
|
935
|
+
|
|
936
|
+
def __deepcopy__(self, memo: Optional[Dict[int, Any]]) -> Domain:
|
|
937
|
+
"""Enables making a deep copy of the `Domain` using `copy.deepcopy`.
|
|
938
|
+
|
|
939
|
+
See https://docs.python.org/3/library/copy.html#copy.deepcopy
|
|
940
|
+
for more implementation.
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
memo: Optional dictionary of objects already copied during the current
|
|
944
|
+
copying pass.
|
|
945
|
+
|
|
946
|
+
Returns:
|
|
947
|
+
A deep copy of the current domain.
|
|
948
|
+
"""
|
|
949
|
+
domain_dict = self.as_dict()
|
|
950
|
+
return self.__class__.from_dict(copy.deepcopy(domain_dict, memo))
|
|
951
|
+
|
|
952
|
+
def count_conditional_response_variations(self) -> int:
|
|
953
|
+
"""Returns count of conditional response variations."""
|
|
954
|
+
count = 0
|
|
955
|
+
for response_variations in self.responses.values():
|
|
956
|
+
for variation in response_variations:
|
|
957
|
+
if RESPONSE_CONDITION in variation:
|
|
958
|
+
count += 1
|
|
959
|
+
|
|
960
|
+
return count
|
|
961
|
+
|
|
962
|
+
@staticmethod
|
|
963
|
+
def _collect_overridden_default_intents(
|
|
964
|
+
intents: Union[Set[Text], List[Text], List[Dict[Text, Any]]],
|
|
965
|
+
) -> List[Text]:
|
|
966
|
+
"""Collects the default intents overridden by the user.
|
|
967
|
+
|
|
968
|
+
Args:
|
|
969
|
+
intents: User-provided intents.
|
|
970
|
+
|
|
971
|
+
Returns:
|
|
972
|
+
User-defined intents that are default intents.
|
|
973
|
+
"""
|
|
974
|
+
intent_names: Set[Text] = {
|
|
975
|
+
next(iter(intent.keys())) if isinstance(intent, dict) else intent
|
|
976
|
+
for intent in intents
|
|
977
|
+
}
|
|
978
|
+
return sorted(
|
|
979
|
+
intent_names.intersection(set(rasa.shared.core.constants.DEFAULT_INTENTS))
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
@staticmethod
|
|
983
|
+
def _initialize_forms(
|
|
984
|
+
forms: Dict[Text, Any],
|
|
985
|
+
) -> Tuple[List[Text], Dict[Text, Any], List[Text]]:
|
|
986
|
+
"""Retrieves the initial values for the Domain's form fields.
|
|
987
|
+
|
|
988
|
+
Args:
|
|
989
|
+
forms: Parsed content of the `forms` section in the domain.
|
|
990
|
+
|
|
991
|
+
Returns:
|
|
992
|
+
The form names, a mapping of form names and required slots, and custom
|
|
993
|
+
actions.
|
|
994
|
+
Returning custom actions for each forms means that Rasa Pro should
|
|
995
|
+
not use the default `FormAction` for the forms, but rather a custom action
|
|
996
|
+
for it. This can e.g. be used to run the deprecated Rasa Open Source 1
|
|
997
|
+
`FormAction` which is implemented in the Rasa SDK.
|
|
998
|
+
"""
|
|
999
|
+
for form_name, form_data in forms.items():
|
|
1000
|
+
if form_data is not None and REQUIRED_SLOTS_KEY not in form_data:
|
|
1001
|
+
forms[form_name] = {REQUIRED_SLOTS_KEY: form_data}
|
|
1002
|
+
return list(forms.keys()), forms, []
|
|
1003
|
+
|
|
1004
|
+
def __hash__(self) -> int:
|
|
1005
|
+
"""Returns a unique hash for the domain."""
|
|
1006
|
+
return int(self.fingerprint(), 16)
|
|
1007
|
+
|
|
1008
|
+
@rasa.shared.utils.common.cached_method
|
|
1009
|
+
def fingerprint(self) -> Text:
|
|
1010
|
+
"""Returns a unique hash for the domain which is stable across python runs.
|
|
1011
|
+
|
|
1012
|
+
Returns:
|
|
1013
|
+
fingerprint of the domain
|
|
1014
|
+
"""
|
|
1015
|
+
self_as_dict = self.as_dict()
|
|
1016
|
+
transformed_intents: List[Text] = []
|
|
1017
|
+
for intent in self_as_dict.get(KEY_INTENTS, []):
|
|
1018
|
+
if isinstance(intent, dict):
|
|
1019
|
+
transformed_intents.append(*intent.keys())
|
|
1020
|
+
elif isinstance(intent, str):
|
|
1021
|
+
transformed_intents.append(intent)
|
|
1022
|
+
|
|
1023
|
+
self_as_dict[KEY_INTENTS] = sorted(transformed_intents)
|
|
1024
|
+
self_as_dict[KEY_ACTIONS] = self.action_names_or_texts
|
|
1025
|
+
return rasa.shared.utils.io.get_dictionary_fingerprint(self_as_dict)
|
|
1026
|
+
|
|
1027
|
+
@staticmethod
|
|
1028
|
+
def _sort_intent_names_alphabetical_order(
|
|
1029
|
+
intents: List[Union[Text, Dict]],
|
|
1030
|
+
) -> List[Union[Text, Dict]]:
|
|
1031
|
+
def sort(elem: Union[Text, Dict]) -> Union[Text, Dict]:
|
|
1032
|
+
if isinstance(elem, dict):
|
|
1033
|
+
return next(iter(elem.keys()))
|
|
1034
|
+
elif isinstance(elem, str):
|
|
1035
|
+
return elem
|
|
1036
|
+
|
|
1037
|
+
sorted_intents = sorted(intents, key=sort)
|
|
1038
|
+
return sorted_intents
|
|
1039
|
+
|
|
1040
|
+
@cached_property
|
|
1041
|
+
def user_actions_and_forms(self) -> List[Text]:
|
|
1042
|
+
"""Returns combination of user actions and forms."""
|
|
1043
|
+
return self.user_actions + self.form_names
|
|
1044
|
+
|
|
1045
|
+
@cached_property
|
|
1046
|
+
def num_actions(self) -> int:
|
|
1047
|
+
"""Returns the number of available actions."""
|
|
1048
|
+
# noinspection PyTypeChecker
|
|
1049
|
+
return len(self.action_names_or_texts)
|
|
1050
|
+
|
|
1051
|
+
@cached_property
|
|
1052
|
+
def num_states(self) -> int:
|
|
1053
|
+
"""Number of used input states for the action prediction."""
|
|
1054
|
+
return len(self.input_states)
|
|
1055
|
+
|
|
1056
|
+
@cached_property
|
|
1057
|
+
def retrieval_intent_responses(self) -> Dict[Text, List[Dict[Text, Any]]]:
|
|
1058
|
+
"""Return only the responses which are defined for retrieval intents."""
|
|
1059
|
+
return dict(
|
|
1060
|
+
filter(
|
|
1061
|
+
lambda intent_response: self.is_retrieval_intent_response(
|
|
1062
|
+
intent_response
|
|
1063
|
+
),
|
|
1064
|
+
self.responses.items(),
|
|
1065
|
+
)
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
@staticmethod
|
|
1069
|
+
def is_retrieval_intent_response(
|
|
1070
|
+
response: Tuple[Text, List[Dict[Text, Any]]],
|
|
1071
|
+
) -> bool:
|
|
1072
|
+
"""Check if the response is for a retrieval intent.
|
|
1073
|
+
|
|
1074
|
+
These responses have a `/` symbol in their name. Use that to filter them from
|
|
1075
|
+
the rest.
|
|
1076
|
+
"""
|
|
1077
|
+
return RESPONSE_IDENTIFIER_DELIMITER in response[0]
|
|
1078
|
+
|
|
1079
|
+
def _add_default_slots(self) -> None:
|
|
1080
|
+
"""Sets up the default slots and slot values for the domain."""
|
|
1081
|
+
self._add_requested_slot()
|
|
1082
|
+
self._add_flow_slots()
|
|
1083
|
+
self._add_knowledge_base_slots()
|
|
1084
|
+
self._add_categorical_slot_default_value()
|
|
1085
|
+
self._add_session_metadata_slot()
|
|
1086
|
+
|
|
1087
|
+
def _add_categorical_slot_default_value(self) -> None:
|
|
1088
|
+
"""Add a default value to all categorical slots.
|
|
1089
|
+
|
|
1090
|
+
All unseen values found for the slot will be mapped to this default value
|
|
1091
|
+
for featurization.
|
|
1092
|
+
"""
|
|
1093
|
+
for slot in [s for s in self.slots if isinstance(s, CategoricalSlot)]:
|
|
1094
|
+
slot.add_default_value()
|
|
1095
|
+
|
|
1096
|
+
def _add_flow_slots(self) -> None:
|
|
1097
|
+
"""Adds the slots needed for the conversation flows."""
|
|
1098
|
+
from rasa.shared.core.constants import FLOW_SLOT_NAMES
|
|
1099
|
+
|
|
1100
|
+
slot_names = [slot.name for slot in self.slots]
|
|
1101
|
+
|
|
1102
|
+
for flow_slot in FLOW_SLOT_NAMES:
|
|
1103
|
+
if flow_slot not in slot_names:
|
|
1104
|
+
self.slots.append(
|
|
1105
|
+
AnySlot(
|
|
1106
|
+
flow_slot,
|
|
1107
|
+
mappings=[],
|
|
1108
|
+
influence_conversation=False,
|
|
1109
|
+
is_builtin=True,
|
|
1110
|
+
)
|
|
1111
|
+
)
|
|
1112
|
+
else:
|
|
1113
|
+
# TODO: in the future we need to prevent this entirely.
|
|
1114
|
+
structlogger.error(
|
|
1115
|
+
"domain.add_flow_slots.slot_reserved_for_internal_usage",
|
|
1116
|
+
event_info=(
|
|
1117
|
+
f"Slot {flow_slot} is reserved for Rasa internal usage, "
|
|
1118
|
+
f"but it already exists. This might lead to bad outcomes."
|
|
1119
|
+
),
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
def _add_requested_slot(self) -> None:
|
|
1123
|
+
"""Add a slot called `requested_slot` to the list of slots.
|
|
1124
|
+
|
|
1125
|
+
The value of this slot will hold the name of the slot which the user
|
|
1126
|
+
needs to fill in next (either explicitly or implicitly) as part of a form.
|
|
1127
|
+
"""
|
|
1128
|
+
if self.form_names and rasa.shared.core.constants.REQUESTED_SLOT not in [
|
|
1129
|
+
slot.name for slot in self.slots
|
|
1130
|
+
]:
|
|
1131
|
+
self.slots.append(
|
|
1132
|
+
TextSlot(
|
|
1133
|
+
rasa.shared.core.constants.REQUESTED_SLOT,
|
|
1134
|
+
mappings=[],
|
|
1135
|
+
influence_conversation=False,
|
|
1136
|
+
is_builtin=True,
|
|
1137
|
+
)
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
def _add_knowledge_base_slots(self) -> None:
|
|
1141
|
+
"""Add slots for the knowledge base action to slots.
|
|
1142
|
+
|
|
1143
|
+
Slots are only added if the default knowledge base action name is present.
|
|
1144
|
+
|
|
1145
|
+
As soon as the knowledge base action is not experimental anymore, we should
|
|
1146
|
+
consider creating a new section in the domain file dedicated to knowledge
|
|
1147
|
+
base slots.
|
|
1148
|
+
"""
|
|
1149
|
+
if (
|
|
1150
|
+
rasa.shared.core.constants.DEFAULT_KNOWLEDGE_BASE_ACTION
|
|
1151
|
+
in self.action_names_or_texts
|
|
1152
|
+
):
|
|
1153
|
+
structlogger.warning(
|
|
1154
|
+
"domain.add_knowledge_base_slots.use_of_experimental_feature",
|
|
1155
|
+
event_info=(
|
|
1156
|
+
"You are using an experimental feature: Action '{}'!".format(
|
|
1157
|
+
rasa.shared.core.constants.DEFAULT_KNOWLEDGE_BASE_ACTION
|
|
1158
|
+
)
|
|
1159
|
+
),
|
|
1160
|
+
)
|
|
1161
|
+
slot_names = [slot.name for slot in self.slots]
|
|
1162
|
+
for slot in KNOWLEDGE_BASE_SLOT_NAMES:
|
|
1163
|
+
if slot not in slot_names:
|
|
1164
|
+
self.slots.append(
|
|
1165
|
+
TextSlot(
|
|
1166
|
+
slot,
|
|
1167
|
+
mappings=[],
|
|
1168
|
+
influence_conversation=False,
|
|
1169
|
+
is_builtin=True,
|
|
1170
|
+
)
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
def _add_session_metadata_slot(self) -> None:
|
|
1174
|
+
self.slots.append(
|
|
1175
|
+
AnySlot(
|
|
1176
|
+
rasa.shared.core.constants.SESSION_START_METADATA_SLOT,
|
|
1177
|
+
mappings=[],
|
|
1178
|
+
is_builtin=True,
|
|
1179
|
+
)
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
def index_for_action(self, action_name: Text) -> int:
|
|
1183
|
+
"""Looks up which action index corresponds to this action name."""
|
|
1184
|
+
try:
|
|
1185
|
+
return self.action_names_or_texts.index(action_name)
|
|
1186
|
+
except ValueError:
|
|
1187
|
+
self.raise_action_not_found_exception(action_name)
|
|
1188
|
+
|
|
1189
|
+
def raise_action_not_found_exception(self, action_name_or_text: Text) -> NoReturn:
|
|
1190
|
+
"""Raises exception if action name or text not part of the domain or stories.
|
|
1191
|
+
|
|
1192
|
+
Args:
|
|
1193
|
+
action_name_or_text: Name of an action or its text in case it's an
|
|
1194
|
+
end-to-end bot utterance.
|
|
1195
|
+
|
|
1196
|
+
Raises:
|
|
1197
|
+
ActionNotFoundException: If `action_name_or_text` are not part of this
|
|
1198
|
+
domain.
|
|
1199
|
+
"""
|
|
1200
|
+
action_names = "\n".join([f"\t - {a}" for a in self.action_names_or_texts])
|
|
1201
|
+
raise ActionNotFoundException(
|
|
1202
|
+
f"Cannot access action '{action_name_or_text}', "
|
|
1203
|
+
f"as that name is not a registered "
|
|
1204
|
+
f"action for this domain. "
|
|
1205
|
+
f"Available actions are: \n{action_names}"
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
@cached_property
|
|
1209
|
+
def slot_states(self) -> List[Text]:
|
|
1210
|
+
"""Returns all available slot state strings."""
|
|
1211
|
+
return [
|
|
1212
|
+
f"{slot.name}_{feature_index}"
|
|
1213
|
+
for slot in self.slots
|
|
1214
|
+
for feature_index in range(0, slot.feature_dimensionality())
|
|
1215
|
+
]
|
|
1216
|
+
|
|
1217
|
+
@cached_property
|
|
1218
|
+
def entity_states(self) -> List[Text]:
|
|
1219
|
+
"""Returns all available entity state strings."""
|
|
1220
|
+
entity_states = copy.deepcopy(self.entities)
|
|
1221
|
+
entity_states.extend(
|
|
1222
|
+
Domain.concatenate_entity_labels(self.entity_properties.roles)
|
|
1223
|
+
)
|
|
1224
|
+
entity_states.extend(
|
|
1225
|
+
Domain.concatenate_entity_labels(self.entity_properties.groups)
|
|
1226
|
+
)
|
|
1227
|
+
|
|
1228
|
+
return entity_states
|
|
1229
|
+
|
|
1230
|
+
@staticmethod
|
|
1231
|
+
def concatenate_entity_labels(
|
|
1232
|
+
entity_labels: Dict[Text, List[Text]], entity: Optional[Text] = None
|
|
1233
|
+
) -> List[Text]:
|
|
1234
|
+
"""Concatenates the given entity labels with their corresponding sub-labels.
|
|
1235
|
+
|
|
1236
|
+
If a specific entity label is given, only this entity label will be
|
|
1237
|
+
concatenated with its corresponding sub-labels.
|
|
1238
|
+
|
|
1239
|
+
Args:
|
|
1240
|
+
entity_labels: A map of an entity label to its sub-label list.
|
|
1241
|
+
entity: If present, only this entity will be considered.
|
|
1242
|
+
|
|
1243
|
+
Returns:
|
|
1244
|
+
A list of labels.
|
|
1245
|
+
"""
|
|
1246
|
+
if entity is not None and entity not in entity_labels:
|
|
1247
|
+
return []
|
|
1248
|
+
|
|
1249
|
+
if entity:
|
|
1250
|
+
return [
|
|
1251
|
+
f"{entity}"
|
|
1252
|
+
f"{rasa.shared.core.constants.ENTITY_LABEL_SEPARATOR}"
|
|
1253
|
+
f"{sub_label}"
|
|
1254
|
+
for sub_label in entity_labels[entity]
|
|
1255
|
+
]
|
|
1256
|
+
|
|
1257
|
+
return [
|
|
1258
|
+
f"{entity_label}"
|
|
1259
|
+
f"{rasa.shared.core.constants.ENTITY_LABEL_SEPARATOR}"
|
|
1260
|
+
f"{entity_sub_label}"
|
|
1261
|
+
for entity_label, entity_sub_labels in entity_labels.items()
|
|
1262
|
+
for entity_sub_label in entity_sub_labels
|
|
1263
|
+
]
|
|
1264
|
+
|
|
1265
|
+
@cached_property
|
|
1266
|
+
def input_state_map(self) -> Dict[Text, int]:
|
|
1267
|
+
"""Provide a mapping from state names to indices."""
|
|
1268
|
+
return {f: i for i, f in enumerate(self.input_states)}
|
|
1269
|
+
|
|
1270
|
+
@cached_property
|
|
1271
|
+
def input_states(self) -> List[Text]:
|
|
1272
|
+
"""Returns all available states."""
|
|
1273
|
+
return (
|
|
1274
|
+
self.intents
|
|
1275
|
+
+ self.entity_states
|
|
1276
|
+
+ self.slot_states
|
|
1277
|
+
+ self.action_names_or_texts
|
|
1278
|
+
+ self.form_names
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
def _get_featurized_entities(self, latest_message: UserUttered) -> Set[Text]:
|
|
1282
|
+
"""Gets the names of all entities that are present and wanted in the message.
|
|
1283
|
+
|
|
1284
|
+
Wherever an entity has a role or group specified as well, an additional role-
|
|
1285
|
+
or group-specific entity name is added.
|
|
1286
|
+
"""
|
|
1287
|
+
intent_name = latest_message.intent.get(INTENT_NAME_KEY)
|
|
1288
|
+
intent_config = self.intent_config(intent_name)
|
|
1289
|
+
entities = latest_message.entities
|
|
1290
|
+
|
|
1291
|
+
# If Entity Roles and Groups is used, we also need to make sure the roles and
|
|
1292
|
+
# groups get featurized. We concatenate the entity label with the role/group
|
|
1293
|
+
# label using a special separator to make sure that the resulting label is
|
|
1294
|
+
# unique (as you can have the same role/group label for different entities).
|
|
1295
|
+
entity_names_basic = set(
|
|
1296
|
+
entity["entity"] for entity in entities if "entity" in entity.keys()
|
|
1297
|
+
)
|
|
1298
|
+
entity_names_roles = set(
|
|
1299
|
+
f"{entity['entity']}"
|
|
1300
|
+
f"{rasa.shared.core.constants.ENTITY_LABEL_SEPARATOR}{entity['role']}"
|
|
1301
|
+
for entity in entities
|
|
1302
|
+
if "entity" in entity.keys() and "role" in entity.keys()
|
|
1303
|
+
)
|
|
1304
|
+
entity_names_groups = set(
|
|
1305
|
+
f"{entity['entity']}"
|
|
1306
|
+
f"{rasa.shared.core.constants.ENTITY_LABEL_SEPARATOR}{entity['group']}"
|
|
1307
|
+
for entity in entities
|
|
1308
|
+
if "entity" in entity.keys() and "group" in entity.keys()
|
|
1309
|
+
)
|
|
1310
|
+
entity_names = entity_names_basic.union(entity_names_roles, entity_names_groups)
|
|
1311
|
+
|
|
1312
|
+
# the USED_ENTITIES_KEY of an intent also contains the entity labels and the
|
|
1313
|
+
# concatenated entity labels with their corresponding roles and groups labels
|
|
1314
|
+
wanted_entities = set(intent_config.get(USED_ENTITIES_KEY, entity_names))
|
|
1315
|
+
|
|
1316
|
+
return entity_names.intersection(wanted_entities)
|
|
1317
|
+
|
|
1318
|
+
def _get_user_sub_state(self, tracker: "DialogueStateTracker") -> SubState:
|
|
1319
|
+
"""Turns latest UserUttered event into a substate.
|
|
1320
|
+
|
|
1321
|
+
The substate will contain intent, text, and entities (if any are present).
|
|
1322
|
+
|
|
1323
|
+
Args:
|
|
1324
|
+
tracker: dialog state tracker containing the dialog so far
|
|
1325
|
+
Returns:
|
|
1326
|
+
a dictionary containing intent, text and set entities
|
|
1327
|
+
"""
|
|
1328
|
+
# proceed with values only if the user of a bot have done something
|
|
1329
|
+
# at the previous step i.e., when the state is not empty.
|
|
1330
|
+
latest_message = tracker.latest_message
|
|
1331
|
+
if not latest_message or latest_message.is_empty():
|
|
1332
|
+
return {}
|
|
1333
|
+
|
|
1334
|
+
sub_state = cast(SubState, latest_message.as_sub_state())
|
|
1335
|
+
|
|
1336
|
+
# Filter entities based on intent config. We need to convert the set into a
|
|
1337
|
+
# tuple because sub_state will be later transformed into a frozenset (so it can
|
|
1338
|
+
# be hashed for deduplication).
|
|
1339
|
+
entities = tuple(
|
|
1340
|
+
self._get_featurized_entities(latest_message).intersection(
|
|
1341
|
+
set(sub_state.get(ENTITIES, ()))
|
|
1342
|
+
)
|
|
1343
|
+
)
|
|
1344
|
+
# Sort entities so that any derived state representation is consistent across
|
|
1345
|
+
# runs and invariant to the order in which the entities for an utterance are
|
|
1346
|
+
# listed in data files.
|
|
1347
|
+
entities = tuple(sorted(entities))
|
|
1348
|
+
|
|
1349
|
+
if entities:
|
|
1350
|
+
sub_state[ENTITIES] = entities
|
|
1351
|
+
else:
|
|
1352
|
+
sub_state.pop(ENTITIES, None)
|
|
1353
|
+
|
|
1354
|
+
return sub_state
|
|
1355
|
+
|
|
1356
|
+
@staticmethod
|
|
1357
|
+
def _get_slots_sub_state(
|
|
1358
|
+
tracker: "DialogueStateTracker", omit_unset_slots: bool = False
|
|
1359
|
+
) -> SubState:
|
|
1360
|
+
"""Sets all set slots with the featurization of the stored value.
|
|
1361
|
+
|
|
1362
|
+
Args:
|
|
1363
|
+
tracker: dialog state tracker containing the dialog so far
|
|
1364
|
+
omit_unset_slots: If `True` do not include the initial values of slots.
|
|
1365
|
+
|
|
1366
|
+
Returns:
|
|
1367
|
+
a mapping of slot names to their featurization
|
|
1368
|
+
"""
|
|
1369
|
+
slots: SubState = {}
|
|
1370
|
+
for slot_name, slot in tracker.slots.items():
|
|
1371
|
+
# If the slot doesn't influence conversations, slot.as_feature() will return
|
|
1372
|
+
# a result that evaluates to False, meaning that the slot shouldn't be
|
|
1373
|
+
# included in featurised sub-states.
|
|
1374
|
+
# Note that this condition checks if the slot itself is None. An unset slot
|
|
1375
|
+
# will be a Slot object and its `value` attribute will be None.
|
|
1376
|
+
if slot is not None and slot.as_feature():
|
|
1377
|
+
if omit_unset_slots and not slot.has_been_set:
|
|
1378
|
+
continue
|
|
1379
|
+
if slot.value == rasa.shared.core.constants.SHOULD_NOT_BE_SET:
|
|
1380
|
+
slots[slot_name] = rasa.shared.core.constants.SHOULD_NOT_BE_SET
|
|
1381
|
+
elif any(slot.as_feature()):
|
|
1382
|
+
# Only include slot in featurised sub-state if the slot is not
|
|
1383
|
+
# unset, i.e. is set to some actual value and has been successfully
|
|
1384
|
+
# featurized, and hence has at least one non-zero feature.
|
|
1385
|
+
slots[slot_name] = tuple(slot.as_feature())
|
|
1386
|
+
return slots
|
|
1387
|
+
|
|
1388
|
+
@staticmethod
|
|
1389
|
+
def _get_prev_action_sub_state(
|
|
1390
|
+
tracker: "DialogueStateTracker",
|
|
1391
|
+
) -> Optional[Dict[Text, Text]]:
|
|
1392
|
+
"""Turn the previous taken action into a state name.
|
|
1393
|
+
|
|
1394
|
+
Args:
|
|
1395
|
+
tracker: dialog state tracker containing the dialog so far
|
|
1396
|
+
Returns:
|
|
1397
|
+
a dictionary with the information on latest action
|
|
1398
|
+
"""
|
|
1399
|
+
return tracker.latest_action
|
|
1400
|
+
|
|
1401
|
+
@staticmethod
|
|
1402
|
+
def _get_active_loop_sub_state(
|
|
1403
|
+
tracker: "DialogueStateTracker",
|
|
1404
|
+
) -> Dict[Text, Optional[Text]]:
|
|
1405
|
+
"""Turn tracker's active loop into a state name.
|
|
1406
|
+
|
|
1407
|
+
Args:
|
|
1408
|
+
tracker: dialog state tracker containing the dialog so far
|
|
1409
|
+
Returns:
|
|
1410
|
+
a dictionary mapping "name" to active loop name if present
|
|
1411
|
+
"""
|
|
1412
|
+
# we don't use tracker.active_loop_name
|
|
1413
|
+
# because we need to keep should_not_be_set
|
|
1414
|
+
if tracker.active_loop:
|
|
1415
|
+
return {rasa.shared.core.constants.LOOP_NAME: tracker.active_loop.name}
|
|
1416
|
+
else:
|
|
1417
|
+
return {}
|
|
1418
|
+
|
|
1419
|
+
@staticmethod
|
|
1420
|
+
def _clean_state(state: State) -> State:
|
|
1421
|
+
return {
|
|
1422
|
+
state_type: sub_state
|
|
1423
|
+
for state_type, sub_state in state.items()
|
|
1424
|
+
if sub_state
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
def get_active_state(
|
|
1428
|
+
self, tracker: "DialogueStateTracker", omit_unset_slots: bool = False
|
|
1429
|
+
) -> State:
|
|
1430
|
+
"""Given a dialogue tracker, makes a representation of current dialogue state.
|
|
1431
|
+
|
|
1432
|
+
Args:
|
|
1433
|
+
tracker: dialog state tracker containing the dialog so far
|
|
1434
|
+
omit_unset_slots: If `True` do not include the initial values of slots.
|
|
1435
|
+
|
|
1436
|
+
Returns:
|
|
1437
|
+
A representation of the dialogue's current state.
|
|
1438
|
+
"""
|
|
1439
|
+
state = {
|
|
1440
|
+
rasa.shared.core.constants.USER: self._get_user_sub_state(tracker),
|
|
1441
|
+
rasa.shared.core.constants.SLOTS: self._get_slots_sub_state(
|
|
1442
|
+
tracker, omit_unset_slots=omit_unset_slots
|
|
1443
|
+
),
|
|
1444
|
+
rasa.shared.core.constants.PREVIOUS_ACTION: self._get_prev_action_sub_state(
|
|
1445
|
+
tracker
|
|
1446
|
+
),
|
|
1447
|
+
rasa.shared.core.constants.ACTIVE_LOOP: self._get_active_loop_sub_state(
|
|
1448
|
+
tracker
|
|
1449
|
+
),
|
|
1450
|
+
}
|
|
1451
|
+
return self._clean_state(state)
|
|
1452
|
+
|
|
1453
|
+
@staticmethod
|
|
1454
|
+
def _remove_rule_only_features(
|
|
1455
|
+
state: State, rule_only_data: Optional[Dict[Text, Any]]
|
|
1456
|
+
) -> None:
|
|
1457
|
+
if not rule_only_data:
|
|
1458
|
+
return
|
|
1459
|
+
|
|
1460
|
+
rule_only_slots = rule_only_data.get(
|
|
1461
|
+
rasa.shared.core.constants.RULE_ONLY_SLOTS, []
|
|
1462
|
+
)
|
|
1463
|
+
rule_only_loops = rule_only_data.get(
|
|
1464
|
+
rasa.shared.core.constants.RULE_ONLY_LOOPS, []
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
# remove slots which only occur in rules but not in stories
|
|
1468
|
+
if rule_only_slots:
|
|
1469
|
+
for slot in rule_only_slots:
|
|
1470
|
+
state.get(rasa.shared.core.constants.SLOTS, {}).pop(slot, None)
|
|
1471
|
+
# remove active loop which only occur in rules but not in stories
|
|
1472
|
+
if (
|
|
1473
|
+
rule_only_loops
|
|
1474
|
+
and state.get(rasa.shared.core.constants.ACTIVE_LOOP, {}).get(
|
|
1475
|
+
rasa.shared.core.constants.LOOP_NAME
|
|
1476
|
+
)
|
|
1477
|
+
in rule_only_loops
|
|
1478
|
+
):
|
|
1479
|
+
del state[rasa.shared.core.constants.ACTIVE_LOOP]
|
|
1480
|
+
|
|
1481
|
+
@staticmethod
|
|
1482
|
+
def _substitute_rule_only_user_input(state: State, last_ml_state: State) -> None:
|
|
1483
|
+
if not rasa.shared.core.trackers.is_prev_action_listen_in_state(state):
|
|
1484
|
+
if not last_ml_state.get(rasa.shared.core.constants.USER) and state.get(
|
|
1485
|
+
rasa.shared.core.constants.USER
|
|
1486
|
+
):
|
|
1487
|
+
del state[rasa.shared.core.constants.USER]
|
|
1488
|
+
elif last_ml_state.get(rasa.shared.core.constants.USER):
|
|
1489
|
+
state[rasa.shared.core.constants.USER] = last_ml_state[
|
|
1490
|
+
rasa.shared.core.constants.USER
|
|
1491
|
+
]
|
|
1492
|
+
|
|
1493
|
+
def states_for_tracker_history(
|
|
1494
|
+
self,
|
|
1495
|
+
tracker: "DialogueStateTracker",
|
|
1496
|
+
omit_unset_slots: bool = False,
|
|
1497
|
+
ignore_rule_only_turns: bool = False,
|
|
1498
|
+
rule_only_data: Optional[Dict[Text, Any]] = None,
|
|
1499
|
+
) -> List[State]:
|
|
1500
|
+
"""List of states for each state of the tracker's history.
|
|
1501
|
+
|
|
1502
|
+
Args:
|
|
1503
|
+
tracker: Dialogue state tracker containing the dialogue so far.
|
|
1504
|
+
omit_unset_slots: If `True` do not include the initial values of slots.
|
|
1505
|
+
ignore_rule_only_turns: If True ignore dialogue turns that are present
|
|
1506
|
+
only in rules.
|
|
1507
|
+
rule_only_data: Slots and loops,
|
|
1508
|
+
which only occur in rules but not in stories.
|
|
1509
|
+
|
|
1510
|
+
Return:
|
|
1511
|
+
A list of states.
|
|
1512
|
+
"""
|
|
1513
|
+
states: List[State] = []
|
|
1514
|
+
last_ml_action_sub_state = None
|
|
1515
|
+
turn_was_hidden = False
|
|
1516
|
+
for tr, hide_rule_turn in tracker.generate_all_prior_trackers():
|
|
1517
|
+
if ignore_rule_only_turns:
|
|
1518
|
+
# remember previous ml action based on the last non hidden turn
|
|
1519
|
+
# we need this to override previous action in the ml state
|
|
1520
|
+
if not turn_was_hidden:
|
|
1521
|
+
last_ml_action_sub_state = self._get_prev_action_sub_state(tr)
|
|
1522
|
+
|
|
1523
|
+
# followup action or happy path loop prediction
|
|
1524
|
+
# don't change the fact whether dialogue turn should be hidden
|
|
1525
|
+
if (
|
|
1526
|
+
not tr.followup_action
|
|
1527
|
+
and not tr.latest_action_name == tr.active_loop_name
|
|
1528
|
+
):
|
|
1529
|
+
turn_was_hidden = hide_rule_turn
|
|
1530
|
+
|
|
1531
|
+
if turn_was_hidden:
|
|
1532
|
+
continue
|
|
1533
|
+
|
|
1534
|
+
state = self.get_active_state(tr, omit_unset_slots=omit_unset_slots)
|
|
1535
|
+
|
|
1536
|
+
if ignore_rule_only_turns:
|
|
1537
|
+
# clean state from only rule features
|
|
1538
|
+
self._remove_rule_only_features(state, rule_only_data)
|
|
1539
|
+
# make sure user input is the same as for previous state
|
|
1540
|
+
# for non action_listen turns
|
|
1541
|
+
if states:
|
|
1542
|
+
self._substitute_rule_only_user_input(state, states[-1])
|
|
1543
|
+
# substitute previous rule action with last_ml_action_sub_state
|
|
1544
|
+
if last_ml_action_sub_state:
|
|
1545
|
+
# FIXME: better type annotation for `State` would require
|
|
1546
|
+
# a larger refactoring (e.g. switch to dataclass)
|
|
1547
|
+
state[rasa.shared.core.constants.PREVIOUS_ACTION] = cast(
|
|
1548
|
+
SubState,
|
|
1549
|
+
last_ml_action_sub_state,
|
|
1550
|
+
)
|
|
1551
|
+
|
|
1552
|
+
states.append(self._clean_state(state))
|
|
1553
|
+
|
|
1554
|
+
return states
|
|
1555
|
+
|
|
1556
|
+
def slots_for_entities(self, entities: List[Dict[Text, Any]]) -> List[SlotSet]:
|
|
1557
|
+
"""Creates slot events for entities if from_entity mapping matches.
|
|
1558
|
+
|
|
1559
|
+
Args:
|
|
1560
|
+
entities: The list of entities.
|
|
1561
|
+
|
|
1562
|
+
Returns:
|
|
1563
|
+
A list of `SlotSet` events.
|
|
1564
|
+
"""
|
|
1565
|
+
if self.store_entities_as_slots:
|
|
1566
|
+
slot_events = []
|
|
1567
|
+
|
|
1568
|
+
for slot in self.slots:
|
|
1569
|
+
matching_entities = []
|
|
1570
|
+
|
|
1571
|
+
for mapping in slot.mappings:
|
|
1572
|
+
mapping_conditions = mapping.get(MAPPING_CONDITIONS)
|
|
1573
|
+
if mapping[MAPPING_TYPE] != str(SlotMappingType.FROM_ENTITY) or (
|
|
1574
|
+
mapping_conditions
|
|
1575
|
+
and mapping_conditions[0].get(ACTIVE_LOOP) is not None
|
|
1576
|
+
):
|
|
1577
|
+
continue
|
|
1578
|
+
|
|
1579
|
+
for entity in entities:
|
|
1580
|
+
if (
|
|
1581
|
+
entity.get(ENTITY_ATTRIBUTE_TYPE)
|
|
1582
|
+
== mapping.get(ENTITY_ATTRIBUTE_TYPE)
|
|
1583
|
+
and entity.get(ENTITY_ATTRIBUTE_ROLE)
|
|
1584
|
+
== mapping.get(ENTITY_ATTRIBUTE_ROLE)
|
|
1585
|
+
and entity.get(ENTITY_ATTRIBUTE_GROUP)
|
|
1586
|
+
== mapping.get(ENTITY_ATTRIBUTE_GROUP)
|
|
1587
|
+
):
|
|
1588
|
+
matching_entities.append(entity.get("value"))
|
|
1589
|
+
|
|
1590
|
+
if matching_entities:
|
|
1591
|
+
if isinstance(slot, ListSlot):
|
|
1592
|
+
slot_events.append(SlotSet(slot.name, matching_entities))
|
|
1593
|
+
else:
|
|
1594
|
+
slot_events.append(SlotSet(slot.name, matching_entities[-1]))
|
|
1595
|
+
|
|
1596
|
+
return slot_events
|
|
1597
|
+
else:
|
|
1598
|
+
return []
|
|
1599
|
+
|
|
1600
|
+
def persist_specification(self, model_path: Text) -> None:
|
|
1601
|
+
"""Persist the domain specification to storage."""
|
|
1602
|
+
domain_spec_path = os.path.join(model_path, "domain.json")
|
|
1603
|
+
rasa.shared.utils.io.create_directory_for_file(domain_spec_path)
|
|
1604
|
+
|
|
1605
|
+
metadata = {"states": self.input_states}
|
|
1606
|
+
rasa.shared.utils.io.dump_obj_as_json_to_file(domain_spec_path, metadata)
|
|
1607
|
+
|
|
1608
|
+
@classmethod
|
|
1609
|
+
def load_specification(cls, path: Text) -> Dict[Text, Any]:
|
|
1610
|
+
"""Load a domains specification from a dumped model directory."""
|
|
1611
|
+
metadata_path = os.path.join(path, "domain.json")
|
|
1612
|
+
|
|
1613
|
+
return json.loads(rasa.shared.utils.io.read_file(metadata_path))
|
|
1614
|
+
|
|
1615
|
+
def compare_with_specification(self, path: Text) -> bool:
|
|
1616
|
+
"""Compare the domain spec of the current and the loaded domain.
|
|
1617
|
+
|
|
1618
|
+
Throws exception if the loaded domain specification is different
|
|
1619
|
+
to the current domain are different.
|
|
1620
|
+
"""
|
|
1621
|
+
loaded_domain_spec = self.load_specification(path)
|
|
1622
|
+
states = loaded_domain_spec["states"]
|
|
1623
|
+
|
|
1624
|
+
if set(states) != set(self.input_states):
|
|
1625
|
+
missing = ",".join(set(states) - set(self.input_states))
|
|
1626
|
+
additional = ",".join(set(self.input_states) - set(states))
|
|
1627
|
+
raise InvalidDomain(
|
|
1628
|
+
f"Domain specification has changed. "
|
|
1629
|
+
f"You MUST retrain the policy. "
|
|
1630
|
+
f"Detected mismatch in domain specification. "
|
|
1631
|
+
f"The following states have been \n"
|
|
1632
|
+
f"\t - removed: {missing} \n"
|
|
1633
|
+
f"\t - added: {additional} "
|
|
1634
|
+
)
|
|
1635
|
+
else:
|
|
1636
|
+
return True
|
|
1637
|
+
|
|
1638
|
+
def as_dict(self) -> Dict[Text, Any]:
|
|
1639
|
+
"""Return serialized `Domain`."""
|
|
1640
|
+
return self._data
|
|
1641
|
+
|
|
1642
|
+
@staticmethod
|
|
1643
|
+
def get_responses_with_multilines(
|
|
1644
|
+
responses: Dict[Text, List[Dict[Text, Any]]],
|
|
1645
|
+
) -> Dict[Text, List[Dict[Text, Any]]]:
|
|
1646
|
+
"""Returns `responses` with preserved multilines in the `text` key.
|
|
1647
|
+
|
|
1648
|
+
Args:
|
|
1649
|
+
responses: Original `responses`.
|
|
1650
|
+
|
|
1651
|
+
Returns:
|
|
1652
|
+
`responses` with preserved multilines in the `text` key.
|
|
1653
|
+
"""
|
|
1654
|
+
from ruamel.yaml.scalarstring import LiteralScalarString
|
|
1655
|
+
|
|
1656
|
+
final_responses = responses.copy()
|
|
1657
|
+
for utter_action, examples in final_responses.items():
|
|
1658
|
+
for i, example in enumerate(examples):
|
|
1659
|
+
response_text = example.get(KEY_RESPONSES_TEXT, "")
|
|
1660
|
+
if not response_text or "\n" not in response_text:
|
|
1661
|
+
continue
|
|
1662
|
+
# Has new lines, use `LiteralScalarString`
|
|
1663
|
+
final_responses[utter_action][i][KEY_RESPONSES_TEXT] = (
|
|
1664
|
+
LiteralScalarString(response_text)
|
|
1665
|
+
)
|
|
1666
|
+
|
|
1667
|
+
return final_responses
|
|
1668
|
+
|
|
1669
|
+
@staticmethod
|
|
1670
|
+
def _cleaned_data(data: Dict[Text, Any]) -> Dict[Text, Any]:
|
|
1671
|
+
"""Remove empty and redundant keys from merged domain dict.
|
|
1672
|
+
|
|
1673
|
+
Returns:
|
|
1674
|
+
A cleaned dictionary version of the domain.
|
|
1675
|
+
"""
|
|
1676
|
+
return {
|
|
1677
|
+
key: val
|
|
1678
|
+
for key, val in data.items()
|
|
1679
|
+
if val != {} and val != [] and val is not None
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
def persist(self, filename: Union[Text, Path]) -> None:
|
|
1683
|
+
"""Write domain to a file."""
|
|
1684
|
+
as_yaml = self.as_yaml()
|
|
1685
|
+
rasa.shared.utils.io.write_text_file(as_yaml, filename)
|
|
1686
|
+
|
|
1687
|
+
def as_yaml(self) -> Text:
|
|
1688
|
+
"""Dump the `Domain` object as a YAML string.
|
|
1689
|
+
|
|
1690
|
+
This function preserves the orders of the keys in the domain.
|
|
1691
|
+
|
|
1692
|
+
Returns:
|
|
1693
|
+
A string in YAML format representing the domain.
|
|
1694
|
+
"""
|
|
1695
|
+
# setting the `version` key first so that it appears at the top of YAML files
|
|
1696
|
+
# thanks to the `should_preserve_key_order` argument
|
|
1697
|
+
# of `dump_obj_as_yaml_to_string`
|
|
1698
|
+
domain_data: Dict[Text, Any] = {
|
|
1699
|
+
KEY_TRAINING_DATA_FORMAT_VERSION: DoubleQuotedScalarString(
|
|
1700
|
+
LATEST_TRAINING_DATA_FORMAT_VERSION
|
|
1701
|
+
)
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
domain_data.update(self.as_dict())
|
|
1705
|
+
|
|
1706
|
+
if domain_data.get(KEY_RESPONSES, {}):
|
|
1707
|
+
domain_data[KEY_RESPONSES] = self.get_responses_with_multilines(
|
|
1708
|
+
domain_data[KEY_RESPONSES]
|
|
1709
|
+
)
|
|
1710
|
+
|
|
1711
|
+
return dump_obj_as_yaml_to_string(domain_data, should_preserve_key_order=True)
|
|
1712
|
+
|
|
1713
|
+
def intent_config(self, intent_name: Text) -> Dict[Text, Any]:
|
|
1714
|
+
"""Return the configuration for an intent."""
|
|
1715
|
+
return self.intent_properties.get(intent_name, {})
|
|
1716
|
+
|
|
1717
|
+
@cached_property
|
|
1718
|
+
def intents(self) -> List[Text]:
|
|
1719
|
+
"""Returns sorted list of intents."""
|
|
1720
|
+
return sorted(self.intent_properties.keys())
|
|
1721
|
+
|
|
1722
|
+
@cached_property
|
|
1723
|
+
def entities(self) -> List[Text]:
|
|
1724
|
+
"""Returns sorted list of entities."""
|
|
1725
|
+
return sorted(self.entity_properties.entities)
|
|
1726
|
+
|
|
1727
|
+
@property
|
|
1728
|
+
def _slots_for_domain_warnings(self) -> List[Text]:
|
|
1729
|
+
"""Fetch names of slots that are used in domain warnings.
|
|
1730
|
+
|
|
1731
|
+
Excludes slots which aren't featurized.
|
|
1732
|
+
"""
|
|
1733
|
+
return [slot.name for slot in self._user_slots if slot.influence_conversation]
|
|
1734
|
+
|
|
1735
|
+
@property
|
|
1736
|
+
def _actions_for_domain_warnings(self) -> List[Text]:
|
|
1737
|
+
"""Fetch names of actions that are used in domain warnings.
|
|
1738
|
+
|
|
1739
|
+
Includes user and form actions, but excludes those that are default actions.
|
|
1740
|
+
"""
|
|
1741
|
+
return [
|
|
1742
|
+
action
|
|
1743
|
+
for action in self.user_actions_and_forms
|
|
1744
|
+
if action not in rasa.shared.core.constants.DEFAULT_ACTION_NAMES
|
|
1745
|
+
]
|
|
1746
|
+
|
|
1747
|
+
@staticmethod
|
|
1748
|
+
def _get_symmetric_difference(
|
|
1749
|
+
domain_elements: Union[List[Text], Set[Text]],
|
|
1750
|
+
training_data_elements: Optional[Union[List[Text], Set[Text]]],
|
|
1751
|
+
) -> Dict[Text, Set[Text]]:
|
|
1752
|
+
"""Gets the symmetric difference between two sets.
|
|
1753
|
+
|
|
1754
|
+
One set represents domain elements and the other one is a set of training
|
|
1755
|
+
data elements.
|
|
1756
|
+
|
|
1757
|
+
Returns a dictionary containing a list of items found in the `domain_elements`
|
|
1758
|
+
but not in `training_data_elements` at key `in_domain`, and a list of items
|
|
1759
|
+
found in `training_data_elements` but not in `domain_elements` at key
|
|
1760
|
+
`in_training_data_set`.
|
|
1761
|
+
"""
|
|
1762
|
+
if training_data_elements is None:
|
|
1763
|
+
training_data_elements = set()
|
|
1764
|
+
|
|
1765
|
+
in_domain_diff = set(domain_elements) - set(training_data_elements)
|
|
1766
|
+
in_training_data_diff = set(training_data_elements) - set(domain_elements)
|
|
1767
|
+
|
|
1768
|
+
return {"in_domain": in_domain_diff, "in_training_data": in_training_data_diff}
|
|
1769
|
+
|
|
1770
|
+
@staticmethod
|
|
1771
|
+
def _combine_with_responses(
|
|
1772
|
+
actions: List[Text], responses: Dict[Text, Any]
|
|
1773
|
+
) -> List[Text]:
|
|
1774
|
+
"""Combines actions with utter actions listed in responses section."""
|
|
1775
|
+
unique_utter_actions = [
|
|
1776
|
+
action for action in sorted(list(responses.keys())) if action not in actions
|
|
1777
|
+
]
|
|
1778
|
+
return actions + unique_utter_actions
|
|
1779
|
+
|
|
1780
|
+
@staticmethod
|
|
1781
|
+
def _combine_user_with_default_actions(user_actions: List[Text]) -> List[Text]:
|
|
1782
|
+
# remove all user actions that overwrite default actions
|
|
1783
|
+
# this logic is a bit reversed, you'd think that we should remove
|
|
1784
|
+
# the action name from the default action names if the user overwrites
|
|
1785
|
+
# the action, but there are some locations in the code where we
|
|
1786
|
+
# implicitly assume that e.g. "action_listen" is always at location
|
|
1787
|
+
# 0 in this array. to keep it that way, we remove the duplicate
|
|
1788
|
+
# action names from the users list instead of the defaults
|
|
1789
|
+
unique_user_actions = [
|
|
1790
|
+
action
|
|
1791
|
+
for action in user_actions
|
|
1792
|
+
if action not in rasa.shared.core.constants.DEFAULT_ACTION_NAMES
|
|
1793
|
+
]
|
|
1794
|
+
return rasa.shared.core.constants.DEFAULT_ACTION_NAMES + unique_user_actions
|
|
1795
|
+
|
|
1796
|
+
def domain_warnings(
|
|
1797
|
+
self,
|
|
1798
|
+
intents: Optional[Union[List[Text], Set[Text]]] = None,
|
|
1799
|
+
entities: Optional[Union[List[Text], Set[Text]]] = None,
|
|
1800
|
+
actions: Optional[Union[List[Text], Set[Text]]] = None,
|
|
1801
|
+
slots: Optional[Union[List[Text], Set[Text]]] = None,
|
|
1802
|
+
) -> Dict[Text, Dict[Text, Set[Text]]]:
|
|
1803
|
+
"""Generate domain warnings from intents, entities, actions and slots.
|
|
1804
|
+
|
|
1805
|
+
Returns a dictionary with entries for `intent_warnings`,
|
|
1806
|
+
`entity_warnings`, `action_warnings` and `slot_warnings`. Excludes domain slots
|
|
1807
|
+
from domain warnings in case they are not featurized.
|
|
1808
|
+
"""
|
|
1809
|
+
intent_warnings = self._get_symmetric_difference(self.intents, intents)
|
|
1810
|
+
entity_warnings = self._get_symmetric_difference(self.entities, entities)
|
|
1811
|
+
action_warnings = self._get_symmetric_difference(
|
|
1812
|
+
self._actions_for_domain_warnings, actions
|
|
1813
|
+
)
|
|
1814
|
+
slot_warnings = self._get_symmetric_difference(
|
|
1815
|
+
self._slots_for_domain_warnings, slots
|
|
1816
|
+
)
|
|
1817
|
+
|
|
1818
|
+
return {
|
|
1819
|
+
"intent_warnings": intent_warnings,
|
|
1820
|
+
"entity_warnings": entity_warnings,
|
|
1821
|
+
"action_warnings": action_warnings,
|
|
1822
|
+
"slot_warnings": slot_warnings,
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
def _check_domain_sanity(self) -> None:
|
|
1826
|
+
"""Make sure the domain is properly configured.
|
|
1827
|
+
|
|
1828
|
+
If the domain contains any duplicate slots, intents, actions
|
|
1829
|
+
or entities, an InvalidDomain error is raised. This error
|
|
1830
|
+
is also raised when intent-action mappings are incorrectly
|
|
1831
|
+
named or a response is missing.
|
|
1832
|
+
"""
|
|
1833
|
+
|
|
1834
|
+
def get_duplicates(my_items: Iterable[Any]) -> List[Any]:
|
|
1835
|
+
"""Returns a list of duplicate items in my_items."""
|
|
1836
|
+
return [
|
|
1837
|
+
item
|
|
1838
|
+
for item, count in collections.Counter(my_items).items()
|
|
1839
|
+
if count > 1
|
|
1840
|
+
]
|
|
1841
|
+
|
|
1842
|
+
def check_mappings(
|
|
1843
|
+
intent_properties: Dict[Text, Dict[Text, Union[bool, List]]],
|
|
1844
|
+
) -> List[Tuple[Text, Text]]:
|
|
1845
|
+
"""Checks whether intent-action mappings use valid action names or texts."""
|
|
1846
|
+
incorrect = []
|
|
1847
|
+
for intent, properties in intent_properties.items():
|
|
1848
|
+
if "triggers" in properties:
|
|
1849
|
+
triggered_action = properties.get("triggers")
|
|
1850
|
+
if triggered_action not in self.action_names_or_texts:
|
|
1851
|
+
incorrect.append((intent, str(triggered_action)))
|
|
1852
|
+
return incorrect
|
|
1853
|
+
|
|
1854
|
+
def get_exception_message(
|
|
1855
|
+
duplicates: Optional[List[Tuple[List[Text], Text]]] = None,
|
|
1856
|
+
mappings: Optional[List[Tuple[Text, Text]]] = None,
|
|
1857
|
+
) -> Text:
|
|
1858
|
+
"""Return a message given a list of error locations."""
|
|
1859
|
+
message = ""
|
|
1860
|
+
if duplicates:
|
|
1861
|
+
message += get_duplicate_exception_message(duplicates)
|
|
1862
|
+
if mappings:
|
|
1863
|
+
if message:
|
|
1864
|
+
message += "\n"
|
|
1865
|
+
message += get_mapping_exception_message(mappings)
|
|
1866
|
+
return message
|
|
1867
|
+
|
|
1868
|
+
def get_mapping_exception_message(mappings: List[Tuple[Text, Text]]) -> Text:
|
|
1869
|
+
"""Return a message given a list of duplicates."""
|
|
1870
|
+
message = ""
|
|
1871
|
+
for name, action_name in mappings:
|
|
1872
|
+
if message:
|
|
1873
|
+
message += "\n"
|
|
1874
|
+
message += (
|
|
1875
|
+
"Intent '{}' is set to trigger action '{}', which is "
|
|
1876
|
+
"not defined in the domain.".format(name, action_name)
|
|
1877
|
+
)
|
|
1878
|
+
return message
|
|
1879
|
+
|
|
1880
|
+
def get_duplicate_exception_message(
|
|
1881
|
+
duplicates: List[Tuple[List[Text], Text]],
|
|
1882
|
+
) -> Text:
|
|
1883
|
+
"""Return a message given a list of duplicates."""
|
|
1884
|
+
message = ""
|
|
1885
|
+
for d, name in duplicates:
|
|
1886
|
+
if d:
|
|
1887
|
+
if message:
|
|
1888
|
+
message += "\n"
|
|
1889
|
+
message += (
|
|
1890
|
+
f"Duplicate {name} in domain. "
|
|
1891
|
+
f"These {name} occur more than once in "
|
|
1892
|
+
f"the domain: '{', '.join(d)}'."
|
|
1893
|
+
)
|
|
1894
|
+
return message
|
|
1895
|
+
|
|
1896
|
+
duplicate_actions = get_duplicates(self.action_names_or_texts)
|
|
1897
|
+
duplicate_slots = get_duplicates([s.name for s in self.slots])
|
|
1898
|
+
duplicate_entities = get_duplicates(self.entities)
|
|
1899
|
+
incorrect_mappings = check_mappings(self.intent_properties)
|
|
1900
|
+
|
|
1901
|
+
if (
|
|
1902
|
+
duplicate_actions
|
|
1903
|
+
or duplicate_slots
|
|
1904
|
+
or duplicate_entities
|
|
1905
|
+
or incorrect_mappings
|
|
1906
|
+
):
|
|
1907
|
+
raise InvalidDomain(
|
|
1908
|
+
get_exception_message(
|
|
1909
|
+
[
|
|
1910
|
+
(duplicate_actions, KEY_ACTIONS),
|
|
1911
|
+
(duplicate_slots, KEY_SLOTS),
|
|
1912
|
+
(duplicate_entities, KEY_ENTITIES),
|
|
1913
|
+
],
|
|
1914
|
+
incorrect_mappings,
|
|
1915
|
+
)
|
|
1916
|
+
)
|
|
1917
|
+
|
|
1918
|
+
@property
|
|
1919
|
+
def utterances_for_response(self) -> Set[Text]:
|
|
1920
|
+
"""Returns utterance set which should have a response.
|
|
1921
|
+
|
|
1922
|
+
Will filter out utterances which are subintent (retrieval intent) types.
|
|
1923
|
+
eg. if actions have ['utter_chitchat', 'utter_chitchat/greet'], this
|
|
1924
|
+
will only return ['utter_chitchat/greet'] as only that will need a
|
|
1925
|
+
response.
|
|
1926
|
+
"""
|
|
1927
|
+
utterances = set()
|
|
1928
|
+
subintent_parents = set()
|
|
1929
|
+
for action in self.action_names_or_texts:
|
|
1930
|
+
if not action.startswith(rasa.shared.constants.UTTER_PREFIX):
|
|
1931
|
+
continue
|
|
1932
|
+
action_parent_split = action.split(RESPONSE_IDENTIFIER_DELIMITER)
|
|
1933
|
+
if len(action_parent_split) == 2:
|
|
1934
|
+
action_parent = action_parent_split[0]
|
|
1935
|
+
subintent_parents.add(action_parent)
|
|
1936
|
+
utterances.add(action)
|
|
1937
|
+
return utterances - subintent_parents
|
|
1938
|
+
|
|
1939
|
+
def check_missing_responses(self) -> None:
|
|
1940
|
+
"""Warn user of utterance names which have no specified response."""
|
|
1941
|
+
missing_responses = self.utterances_for_response - set(self.responses)
|
|
1942
|
+
|
|
1943
|
+
for response in missing_responses:
|
|
1944
|
+
structlogger.warning(
|
|
1945
|
+
"domain.check_missing_response",
|
|
1946
|
+
response=response,
|
|
1947
|
+
event_info=(
|
|
1948
|
+
f"Action '{response}' is listed as a "
|
|
1949
|
+
f"response action in the domain file, but there is "
|
|
1950
|
+
f"no matching response defined. Please check your domain."
|
|
1951
|
+
),
|
|
1952
|
+
docs=DOCS_URL_RESPONSES,
|
|
1953
|
+
)
|
|
1954
|
+
|
|
1955
|
+
def is_empty(self) -> bool:
|
|
1956
|
+
"""Check whether the domain is empty."""
|
|
1957
|
+
return self.as_dict() == Domain.empty().as_dict()
|
|
1958
|
+
|
|
1959
|
+
@classmethod
|
|
1960
|
+
def is_domain_file(cls, filename: Union[Text, Path]) -> bool:
|
|
1961
|
+
"""Checks whether the given file path is a Rasa domain file.
|
|
1962
|
+
|
|
1963
|
+
Args:
|
|
1964
|
+
filename: Path of the file which should be checked.
|
|
1965
|
+
|
|
1966
|
+
Returns:
|
|
1967
|
+
`True` if it's a domain file, otherwise `False`.
|
|
1968
|
+
|
|
1969
|
+
Raises:
|
|
1970
|
+
YamlException: if the file seems to be a YAML file (extension) but
|
|
1971
|
+
can not be read / parsed.
|
|
1972
|
+
"""
|
|
1973
|
+
from rasa.shared.data import is_likely_yaml_file
|
|
1974
|
+
|
|
1975
|
+
if not is_likely_yaml_file(filename):
|
|
1976
|
+
return False
|
|
1977
|
+
|
|
1978
|
+
try:
|
|
1979
|
+
content = read_yaml_file(filename, expand_env_vars=cls.expand_env_vars)
|
|
1980
|
+
except (RasaException, YamlSyntaxException):
|
|
1981
|
+
structlogger.warning(
|
|
1982
|
+
"domain.cannot_load_domain_file",
|
|
1983
|
+
file=filename,
|
|
1984
|
+
event_info=(
|
|
1985
|
+
f"The file {filename} could not be loaded as domain file. "
|
|
1986
|
+
f"You can use https://yamlchecker.com/ to validate "
|
|
1987
|
+
f"the YAML syntax of your file."
|
|
1988
|
+
),
|
|
1989
|
+
)
|
|
1990
|
+
return False
|
|
1991
|
+
|
|
1992
|
+
return any(key in content for key in ALL_DOMAIN_KEYS)
|
|
1993
|
+
|
|
1994
|
+
def required_slots_for_form(self, form_name: Text) -> List[Text]:
|
|
1995
|
+
"""Retrieve the list of required slot names for a form defined in the domain.
|
|
1996
|
+
|
|
1997
|
+
Args:
|
|
1998
|
+
form_name: The name of the form.
|
|
1999
|
+
|
|
2000
|
+
Returns:
|
|
2001
|
+
The list of slot names or an empty list if no form was found.
|
|
2002
|
+
"""
|
|
2003
|
+
form = self.forms.get(form_name)
|
|
2004
|
+
if form:
|
|
2005
|
+
return form[REQUIRED_SLOTS_KEY]
|
|
2006
|
+
|
|
2007
|
+
return []
|
|
2008
|
+
|
|
2009
|
+
def count_slot_mapping_statistics(self) -> Tuple[int, int, int]:
|
|
2010
|
+
"""Counts the total number of slot mappings and custom slot mappings.
|
|
2011
|
+
|
|
2012
|
+
Returns:
|
|
2013
|
+
A triple of integers where the first entry is the total number of mappings,
|
|
2014
|
+
the second entry is the total number of custom mappings, and the third entry
|
|
2015
|
+
is the total number of mappings which have conditions attached.
|
|
2016
|
+
"""
|
|
2017
|
+
total_mappings = 0
|
|
2018
|
+
custom_mappings = 0
|
|
2019
|
+
conditional_mappings = 0
|
|
2020
|
+
|
|
2021
|
+
for slot in self.slots:
|
|
2022
|
+
total_mappings += len(slot.mappings)
|
|
2023
|
+
for mapping in slot.mappings:
|
|
2024
|
+
if mapping[MAPPING_TYPE] == str(SlotMappingType.CUSTOM):
|
|
2025
|
+
custom_mappings += 1
|
|
2026
|
+
|
|
2027
|
+
if MAPPING_CONDITIONS in mapping:
|
|
2028
|
+
conditional_mappings += 1
|
|
2029
|
+
|
|
2030
|
+
return (total_mappings, custom_mappings, conditional_mappings)
|
|
2031
|
+
|
|
2032
|
+
def does_custom_action_explicitly_need_domain(self, action_name: Text) -> bool:
|
|
2033
|
+
"""Assert if action has explicitly stated that it needs domain.
|
|
2034
|
+
|
|
2035
|
+
Args:
|
|
2036
|
+
action_name: Name of the action to be checked
|
|
2037
|
+
|
|
2038
|
+
Returns:
|
|
2039
|
+
True if action has explicitly stated that it needs domain.
|
|
2040
|
+
Otherwise, it returns false.
|
|
2041
|
+
"""
|
|
2042
|
+
return action_name in self._actions_which_explicitly_need_domain
|
|
2043
|
+
|
|
2044
|
+
def __repr__(self) -> Text:
|
|
2045
|
+
"""Returns text representation of object."""
|
|
2046
|
+
return (
|
|
2047
|
+
f"{self.__class__.__name__}: {len(self.action_names_or_texts)} actions, "
|
|
2048
|
+
f"{len(self.intent_properties)} intents, {len(self.responses)} responses, "
|
|
2049
|
+
f"{len(self.slots)} slots, "
|
|
2050
|
+
f"{len(self.entities)} entities, {len(self.form_names)} forms"
|
|
2051
|
+
)
|
|
2052
|
+
|
|
2053
|
+
@staticmethod
|
|
2054
|
+
def _collect_action_names(
|
|
2055
|
+
actions: List[Union[Text, Dict[Text, Any]]],
|
|
2056
|
+
) -> List[Text]:
|
|
2057
|
+
action_names: List[Text] = []
|
|
2058
|
+
|
|
2059
|
+
for action in actions:
|
|
2060
|
+
if isinstance(action, dict):
|
|
2061
|
+
action_names.extend(list(action.keys()))
|
|
2062
|
+
elif isinstance(action, str):
|
|
2063
|
+
action_names += [action]
|
|
2064
|
+
|
|
2065
|
+
return action_names
|
|
2066
|
+
|
|
2067
|
+
@staticmethod
|
|
2068
|
+
def _collect_actions_which_explicitly_need_domain(
|
|
2069
|
+
actions: List[Union[Text, Dict[Text, Any]]],
|
|
2070
|
+
) -> List[Text]:
|
|
2071
|
+
action_names: List[Text] = []
|
|
2072
|
+
|
|
2073
|
+
for action in actions:
|
|
2074
|
+
if isinstance(action, dict):
|
|
2075
|
+
for action_name, action_config in action.items():
|
|
2076
|
+
should_send_domain = action_config.get(
|
|
2077
|
+
ACTION_SHOULD_SEND_DOMAIN, False
|
|
2078
|
+
)
|
|
2079
|
+
if should_send_domain:
|
|
2080
|
+
action_names += [action_name]
|
|
2081
|
+
|
|
2082
|
+
elif action.startswith("validate_"):
|
|
2083
|
+
action_names += [action]
|
|
2084
|
+
|
|
2085
|
+
return action_names
|
|
2086
|
+
|
|
2087
|
+
def is_custom_action(self, action_name: str) -> bool:
|
|
2088
|
+
return action_name in self._custom_actions
|
|
2089
|
+
|
|
2090
|
+
@classmethod
|
|
2091
|
+
def _dict_from_raw_yaml_content(cls, raw_yaml_content: Text) -> Any:
|
|
2092
|
+
"""Loads the Domain dict from raw YAML content.
|
|
2093
|
+
|
|
2094
|
+
Validates the raw YAML content using the schema file if `validate_yaml` is set
|
|
2095
|
+
to `True`.
|
|
2096
|
+
|
|
2097
|
+
Args:
|
|
2098
|
+
raw_yaml_content: The raw YAML content of the domain file.
|
|
2099
|
+
|
|
2100
|
+
Returns:
|
|
2101
|
+
The Domain dict.
|
|
2102
|
+
"""
|
|
2103
|
+
if cls.validate_yaml:
|
|
2104
|
+
structlogger.info(
|
|
2105
|
+
"domain.from_yaml.validating",
|
|
2106
|
+
)
|
|
2107
|
+
validate_raw_yaml_using_schema_file_with_responses(
|
|
2108
|
+
raw_yaml_content,
|
|
2109
|
+
DOMAIN_SCHEMA_FILE,
|
|
2110
|
+
expand_env_vars=cls.expand_env_vars,
|
|
2111
|
+
)
|
|
2112
|
+
|
|
2113
|
+
return read_yaml(raw_yaml_content, expand_env_vars=cls.expand_env_vars)
|
|
2114
|
+
|
|
2115
|
+
|
|
2116
|
+
def warn_about_duplicates_found_during_domain_merging(
|
|
2117
|
+
duplicates: Dict[Text, List[Text]],
|
|
2118
|
+
) -> None:
|
|
2119
|
+
"""Emits a warning about found duplicates while loading multiple domain paths."""
|
|
2120
|
+
domain_keys = [
|
|
2121
|
+
KEY_INTENTS,
|
|
2122
|
+
KEY_FORMS,
|
|
2123
|
+
KEY_ACTIONS,
|
|
2124
|
+
KEY_E2E_ACTIONS,
|
|
2125
|
+
KEY_RESPONSES,
|
|
2126
|
+
KEY_SLOTS,
|
|
2127
|
+
KEY_ENTITIES,
|
|
2128
|
+
]
|
|
2129
|
+
|
|
2130
|
+
# Build the message if there are duplicates
|
|
2131
|
+
message = []
|
|
2132
|
+
for key in domain_keys:
|
|
2133
|
+
duplicates_per_key = duplicates.get(key)
|
|
2134
|
+
if duplicates_per_key:
|
|
2135
|
+
message.append(
|
|
2136
|
+
f"The following duplicated {key} have been found "
|
|
2137
|
+
f"across multiple domain files: "
|
|
2138
|
+
f"{', '.join(duplicates_per_key)}"
|
|
2139
|
+
)
|
|
2140
|
+
|
|
2141
|
+
# send the warning with the constructed message
|
|
2142
|
+
if message:
|
|
2143
|
+
full_message = " \n".join(message)
|
|
2144
|
+
structlogger.warning(
|
|
2145
|
+
"domain.duplicates_found", event_info=full_message, docs=DOCS_URL_DOMAINS
|
|
2146
|
+
)
|
|
2147
|
+
|
|
2148
|
+
return None
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
def _validate_forms(forms: Union[Dict, List]) -> None:
|
|
2152
|
+
if not isinstance(forms, dict):
|
|
2153
|
+
raise InvalidDomain("Forms have to be specified as dictionary.")
|
|
2154
|
+
|
|
2155
|
+
for form_name, form_data in forms.items():
|
|
2156
|
+
if form_data is None:
|
|
2157
|
+
continue
|
|
2158
|
+
|
|
2159
|
+
if not isinstance(form_data, Dict):
|
|
2160
|
+
raise InvalidDomain(
|
|
2161
|
+
f"The contents of form '{form_name}' were specified "
|
|
2162
|
+
f"as '{type(form_data)}'. They need to be specified "
|
|
2163
|
+
f"as dictionary. Please see {DOCS_URL_FORMS} "
|
|
2164
|
+
f"for more information."
|
|
2165
|
+
)
|
|
2166
|
+
|
|
2167
|
+
if IGNORED_INTENTS in form_data and REQUIRED_SLOTS_KEY not in form_data:
|
|
2168
|
+
raise InvalidDomain(
|
|
2169
|
+
f"If you use the `{IGNORED_INTENTS}` parameter in your form, then "
|
|
2170
|
+
f"the keyword `{REQUIRED_SLOTS_KEY}` is required. "
|
|
2171
|
+
f"Please see {DOCS_URL_FORMS} for more information."
|
|
2172
|
+
)
|