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,1192 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import copy
|
|
3
|
+
import datetime
|
|
4
|
+
import difflib
|
|
5
|
+
from asyncio import CancelledError
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, DefaultDict, Dict, List, Optional, Text, Tuple, Union
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
import structlog
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
|
|
15
|
+
import rasa.shared.utils.io
|
|
16
|
+
from rasa.core.channels import CollectingOutputChannel, UserMessage
|
|
17
|
+
from rasa.core.constants import ACTIVE_FLOW_METADATA_KEY, STEP_ID_METADATA_KEY
|
|
18
|
+
from rasa.core.exceptions import AgentNotReady
|
|
19
|
+
from rasa.core.persistor import StorageType
|
|
20
|
+
from rasa.core.utils import AvailableEndpoints
|
|
21
|
+
from rasa.e2e_test.constants import TEST_CASE_NAME, TEST_FILE_NAME
|
|
22
|
+
from rasa.e2e_test.e2e_config import create_llm_judge_config
|
|
23
|
+
from rasa.e2e_test.e2e_test_case import (
|
|
24
|
+
KEY_STUB_CUSTOM_ACTIONS,
|
|
25
|
+
ActualStepOutput,
|
|
26
|
+
Fixture,
|
|
27
|
+
Metadata,
|
|
28
|
+
TestCase,
|
|
29
|
+
TestStep,
|
|
30
|
+
)
|
|
31
|
+
from rasa.e2e_test.e2e_test_result import (
|
|
32
|
+
NO_RESPONSE,
|
|
33
|
+
NO_SLOT,
|
|
34
|
+
TestFailure,
|
|
35
|
+
TestResult,
|
|
36
|
+
)
|
|
37
|
+
from rasa.llm_fine_tuning.conversations import Conversation
|
|
38
|
+
from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
39
|
+
from rasa.shared.core.events import (
|
|
40
|
+
ActionExecuted,
|
|
41
|
+
BotUttered,
|
|
42
|
+
Event,
|
|
43
|
+
FlowCompleted,
|
|
44
|
+
FlowStarted,
|
|
45
|
+
SlotSet,
|
|
46
|
+
UserUttered,
|
|
47
|
+
)
|
|
48
|
+
from rasa.shared.core.flows.flow_path import FlowPath, PathNode
|
|
49
|
+
from rasa.shared.core.trackers import DialogueStateTracker
|
|
50
|
+
from rasa.shared.exceptions import RasaException
|
|
51
|
+
from rasa.shared.nlu.constants import COMMANDS
|
|
52
|
+
from rasa.telemetry import track_e2e_test_run
|
|
53
|
+
from rasa.utils.endpoints import EndpointConfig
|
|
54
|
+
|
|
55
|
+
structlogger = structlog.get_logger()
|
|
56
|
+
|
|
57
|
+
TEST_TURNS_TYPE = Dict[int, Union[TestStep, ActualStepOutput]]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class E2ETestRunner:
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
model_path: Optional[Text] = None,
|
|
64
|
+
model_server: Optional[EndpointConfig] = None,
|
|
65
|
+
remote_storage: Optional[StorageType] = None,
|
|
66
|
+
endpoints: Optional[AvailableEndpoints] = None,
|
|
67
|
+
**kwargs: Any,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Initializes the E2E test suite runner.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
model_path: Path to the model.
|
|
73
|
+
model_server: Model server configuration.
|
|
74
|
+
remote_storage: Remote storage to use for model retrieval.
|
|
75
|
+
endpoints: Endpoints configuration.
|
|
76
|
+
**kwargs: Additional arguments
|
|
77
|
+
"""
|
|
78
|
+
import rasa.core.agent
|
|
79
|
+
|
|
80
|
+
structlogger.info(
|
|
81
|
+
"e2e_test_runner.init",
|
|
82
|
+
event_info="Started running end-to-end testing.",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
test_case_path = kwargs.get("test_case_path")
|
|
86
|
+
self.llm_judge_config = create_llm_judge_config(test_case_path)
|
|
87
|
+
|
|
88
|
+
are_custom_actions_stubbed = (
|
|
89
|
+
endpoints
|
|
90
|
+
and endpoints.action
|
|
91
|
+
and endpoints.action.kwargs.get(KEY_STUB_CUSTOM_ACTIONS)
|
|
92
|
+
)
|
|
93
|
+
if endpoints and not are_custom_actions_stubbed:
|
|
94
|
+
self._action_server_is_reachable(endpoints)
|
|
95
|
+
|
|
96
|
+
self.agent = asyncio.run(
|
|
97
|
+
rasa.core.agent.load_agent(
|
|
98
|
+
model_path=model_path,
|
|
99
|
+
model_server=model_server,
|
|
100
|
+
remote_storage=remote_storage,
|
|
101
|
+
endpoints=endpoints,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if not self.agent.is_ready():
|
|
106
|
+
raise AgentNotReady(
|
|
107
|
+
"Agent needs to be prepared before usage. "
|
|
108
|
+
"Please check that the agent was able to "
|
|
109
|
+
"load the trained model."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def run_prediction_loop(
|
|
113
|
+
self,
|
|
114
|
+
collector: CollectingOutputChannel,
|
|
115
|
+
steps: List[TestStep],
|
|
116
|
+
sender_id: Text,
|
|
117
|
+
test_case_metadata: Optional[Metadata] = None,
|
|
118
|
+
input_metadata: Optional[List[Metadata]] = None,
|
|
119
|
+
) -> TEST_TURNS_TYPE:
|
|
120
|
+
"""Runs dialogue prediction.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
collector: Output channel.
|
|
124
|
+
steps: List of steps to run.
|
|
125
|
+
sender_id: The test case name with added timestamp suffix.
|
|
126
|
+
test_case_metadata: Metadata of test case.
|
|
127
|
+
input_metadata: List of metadata.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Test turns: {turn_sequence (int) : TestStep or ActualStepOutput}.
|
|
131
|
+
"""
|
|
132
|
+
turns: TEST_TURNS_TYPE = {}
|
|
133
|
+
event_cursor = 0
|
|
134
|
+
|
|
135
|
+
if not self.agent.processor:
|
|
136
|
+
return turns
|
|
137
|
+
|
|
138
|
+
tracker = await self.agent.processor.fetch_tracker_with_initial_session(
|
|
139
|
+
sender_id, output_channel=collector
|
|
140
|
+
)
|
|
141
|
+
# turn -1 i used to contain events that happen during
|
|
142
|
+
# the start of the session and before the first user message
|
|
143
|
+
# TestStep is a placeholder just for the sake of having a turn
|
|
144
|
+
# to specify the actor
|
|
145
|
+
turns[-1], event_cursor = self.get_actual_step_output(
|
|
146
|
+
tracker,
|
|
147
|
+
TestStep(
|
|
148
|
+
actor="bot",
|
|
149
|
+
text=None,
|
|
150
|
+
),
|
|
151
|
+
event_cursor,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
for position, step in enumerate(steps):
|
|
155
|
+
if step.actor != "user":
|
|
156
|
+
turns[position] = step
|
|
157
|
+
continue
|
|
158
|
+
elif not step.text:
|
|
159
|
+
rasa.shared.utils.io.raise_warning(
|
|
160
|
+
f"The test case '{sender_id}' contains a `user` step in line "
|
|
161
|
+
f"{position + 1} without a text value. "
|
|
162
|
+
f"Skipping this step and proceeding to the next user step.",
|
|
163
|
+
UserWarning,
|
|
164
|
+
)
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
metadata = test_case_metadata.metadata if test_case_metadata else {}
|
|
168
|
+
|
|
169
|
+
if input_metadata:
|
|
170
|
+
step_metadata = self.filter_metadata_for_input(
|
|
171
|
+
step.metadata_name, input_metadata
|
|
172
|
+
)
|
|
173
|
+
step_metadata_dict = step_metadata.metadata if step_metadata else {}
|
|
174
|
+
metadata = self.merge_metadata(
|
|
175
|
+
sender_id, step.text, metadata, step_metadata_dict
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
await self.agent.handle_message(
|
|
180
|
+
UserMessage(
|
|
181
|
+
step.text,
|
|
182
|
+
collector,
|
|
183
|
+
sender_id,
|
|
184
|
+
metadata=metadata,
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
except CancelledError:
|
|
188
|
+
structlogger.error(
|
|
189
|
+
"e2e_test_runner.run_prediction_loop",
|
|
190
|
+
error=f"Message handling timed out for user message '{step.text}'.",
|
|
191
|
+
exc_info=True,
|
|
192
|
+
)
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
structlogger.error(
|
|
195
|
+
"e2e_test_runner.run_prediction_loop",
|
|
196
|
+
error=f"An exception occurred while handling "
|
|
197
|
+
f"user message '{step.text}'. Error: {exc}",
|
|
198
|
+
)
|
|
199
|
+
tracker = await self.agent.tracker_store.retrieve(sender_id) # type: ignore[assignment]
|
|
200
|
+
turns[position], event_cursor = self.get_actual_step_output(
|
|
201
|
+
tracker, step, event_cursor
|
|
202
|
+
)
|
|
203
|
+
return turns
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def merge_metadata(
|
|
207
|
+
sender_id: Text,
|
|
208
|
+
step_text: Text,
|
|
209
|
+
test_case_metadata: Dict[Text, Text],
|
|
210
|
+
step_metadata: Dict[Text, Text],
|
|
211
|
+
) -> Dict[Text, Text]:
|
|
212
|
+
"""Merges the test case and user step metadata.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
sender_id: The test case name with added timestamp suffix.
|
|
216
|
+
step_text: The user step text.
|
|
217
|
+
test_case_metadata: The test case metadata dict.
|
|
218
|
+
step_metadata: The user step metadata dict.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
A dictionary with the merged metadata.
|
|
222
|
+
"""
|
|
223
|
+
if not test_case_metadata:
|
|
224
|
+
return step_metadata
|
|
225
|
+
if not step_metadata:
|
|
226
|
+
return test_case_metadata
|
|
227
|
+
|
|
228
|
+
keys_to_overwrite = []
|
|
229
|
+
|
|
230
|
+
for key in step_metadata.keys():
|
|
231
|
+
if key in test_case_metadata.keys():
|
|
232
|
+
keys_to_overwrite.append(key)
|
|
233
|
+
|
|
234
|
+
if keys_to_overwrite:
|
|
235
|
+
test_case_name = sender_id.rsplit("_", 1)[0]
|
|
236
|
+
structlogger.warning(
|
|
237
|
+
"e2e_test_runner.merge_metadata",
|
|
238
|
+
message=f"Metadata {keys_to_overwrite} exist in both the test case "
|
|
239
|
+
f"'{test_case_name}' and the user step '{step_text}'. "
|
|
240
|
+
"The user step metadata takes precedence and will "
|
|
241
|
+
"override the test case metadata.",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
merged_metadata = copy.deepcopy(test_case_metadata)
|
|
245
|
+
merged_metadata.update(step_metadata)
|
|
246
|
+
|
|
247
|
+
return merged_metadata
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def get_actual_step_output(
|
|
251
|
+
tracker: DialogueStateTracker,
|
|
252
|
+
test_step: TestStep,
|
|
253
|
+
event_cursor: int,
|
|
254
|
+
) -> Tuple[ActualStepOutput, int]:
|
|
255
|
+
"""Returns the events that are generated from the current step.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
tracker: The tracker for the current test case.
|
|
259
|
+
test_step: The test step.
|
|
260
|
+
event_cursor: The event cursor where the previous step left off.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
The events generated from the current step and the updated event
|
|
264
|
+
cursor position.
|
|
265
|
+
"""
|
|
266
|
+
session_events = list(tracker.events)[event_cursor:]
|
|
267
|
+
if session_events:
|
|
268
|
+
event_cursor += len(session_events)
|
|
269
|
+
return (
|
|
270
|
+
ActualStepOutput.from_test_step(
|
|
271
|
+
test_step,
|
|
272
|
+
[
|
|
273
|
+
event
|
|
274
|
+
for event in session_events
|
|
275
|
+
if isinstance(event, (BotUttered, UserUttered, SlotSet))
|
|
276
|
+
],
|
|
277
|
+
),
|
|
278
|
+
event_cursor,
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
structlogger.warning(
|
|
282
|
+
"e2e_test_runner.get_actual_step_output",
|
|
283
|
+
message=f"No events found for '{tracker.sender_id}' after processing "
|
|
284
|
+
f"test step '{test_step.text}'.",
|
|
285
|
+
)
|
|
286
|
+
# if there are no events, we still want to return an
|
|
287
|
+
# ActualStepOutput object with the test step as the
|
|
288
|
+
# response to be able to generate a diff
|
|
289
|
+
return (
|
|
290
|
+
ActualStepOutput.from_test_step(
|
|
291
|
+
test_step,
|
|
292
|
+
[
|
|
293
|
+
UserUttered(text=test_step.text),
|
|
294
|
+
BotUttered(text=NO_RESPONSE),
|
|
295
|
+
],
|
|
296
|
+
),
|
|
297
|
+
event_cursor,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
@classmethod
|
|
301
|
+
def generate_test_result(
|
|
302
|
+
cls,
|
|
303
|
+
test_turns: TEST_TURNS_TYPE,
|
|
304
|
+
test_case: TestCase,
|
|
305
|
+
) -> TestResult:
|
|
306
|
+
"""Generates test result.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
test_turns: the turns that happened when running the test case or test step.
|
|
310
|
+
test_case: the `TestCase` instance.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Test result.
|
|
314
|
+
"""
|
|
315
|
+
difference = []
|
|
316
|
+
error_line = None
|
|
317
|
+
test_failures = cls.find_test_failures(test_turns, test_case)
|
|
318
|
+
if test_failures:
|
|
319
|
+
first_failure = test_failures[0][0]
|
|
320
|
+
difference = cls.human_readable_diff(test_turns, test_failures)
|
|
321
|
+
error_line = first_failure.error_line if first_failure else None
|
|
322
|
+
|
|
323
|
+
return TestResult(
|
|
324
|
+
pass_status=len(test_failures) == 0,
|
|
325
|
+
test_case=test_case,
|
|
326
|
+
difference=difference,
|
|
327
|
+
error_line=error_line,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
def _get_additional_splitting_conditions(
|
|
331
|
+
self,
|
|
332
|
+
step: TestStep,
|
|
333
|
+
input_metadata: List[Metadata],
|
|
334
|
+
tracker: DialogueStateTracker,
|
|
335
|
+
test_case: TestCase,
|
|
336
|
+
) -> Dict[str, Any]:
|
|
337
|
+
"""Returns additional splitting conditions for the user message."""
|
|
338
|
+
additional_splitting_conditions: Dict[str, Any] = {"text": step.text}
|
|
339
|
+
|
|
340
|
+
if not step.metadata_name:
|
|
341
|
+
return additional_splitting_conditions
|
|
342
|
+
|
|
343
|
+
step_metadata = self.filter_metadata_for_input(
|
|
344
|
+
step.metadata_name, input_metadata
|
|
345
|
+
)
|
|
346
|
+
step_metadata_dict = step_metadata.metadata if step_metadata else {}
|
|
347
|
+
|
|
348
|
+
test_case_metadata = self.filter_metadata_for_input(
|
|
349
|
+
test_case.metadata_name, input_metadata
|
|
350
|
+
)
|
|
351
|
+
test_case_metadata_as_dict = (
|
|
352
|
+
test_case_metadata.metadata if test_case_metadata else {}
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
metadata: Dict[str, Any] = self.merge_metadata(
|
|
356
|
+
tracker.sender_id,
|
|
357
|
+
step.text,
|
|
358
|
+
test_case_metadata_as_dict,
|
|
359
|
+
step_metadata_dict,
|
|
360
|
+
)
|
|
361
|
+
metadata["model_id"] = tracker.model_id
|
|
362
|
+
metadata["assistant_id"] = tracker.assistant_id
|
|
363
|
+
|
|
364
|
+
additional_splitting_conditions["metadata"] = metadata
|
|
365
|
+
|
|
366
|
+
return additional_splitting_conditions
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def _get_current_user_turn_and_prior_events(
|
|
370
|
+
tracker: DialogueStateTracker,
|
|
371
|
+
additional_splitting_conditions: Dict[str, Any],
|
|
372
|
+
step: TestStep,
|
|
373
|
+
) -> Tuple[List[Event], List[Event]]:
|
|
374
|
+
"""Returns the current user turn and prior events."""
|
|
375
|
+
actual_events = tracker.events
|
|
376
|
+
|
|
377
|
+
# this returns 2 lists, the first list contains the events until the user
|
|
378
|
+
# message and the second list contains the events after the
|
|
379
|
+
# user message, including the user message
|
|
380
|
+
step_events = rasa.shared.core.events.split_events(
|
|
381
|
+
actual_events,
|
|
382
|
+
UserUttered,
|
|
383
|
+
additional_splitting_conditions=additional_splitting_conditions,
|
|
384
|
+
include_splitting_event=True,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
if len(step_events) < 2:
|
|
388
|
+
structlogger.error(
|
|
389
|
+
"e2e_test_runner.run_assertions.user_message_not_found",
|
|
390
|
+
message=f"User message '{step.text}' was not found in "
|
|
391
|
+
f"the actual events. The user message "
|
|
392
|
+
f"properties which were searched: "
|
|
393
|
+
f"{additional_splitting_conditions}",
|
|
394
|
+
)
|
|
395
|
+
return [], []
|
|
396
|
+
|
|
397
|
+
post_step_events = step_events[1]
|
|
398
|
+
prior_events = step_events[0]
|
|
399
|
+
|
|
400
|
+
# subset of events until the next user message
|
|
401
|
+
turn_events = []
|
|
402
|
+
for event in post_step_events:
|
|
403
|
+
# we reached the next user message
|
|
404
|
+
if isinstance(event, UserUttered) and step.text != event.text:
|
|
405
|
+
break
|
|
406
|
+
|
|
407
|
+
turn_events.append(event)
|
|
408
|
+
|
|
409
|
+
return turn_events, prior_events
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def _slice_turn_events(
|
|
413
|
+
step: TestStep,
|
|
414
|
+
matching_event: Event,
|
|
415
|
+
turn_events: List[Event],
|
|
416
|
+
prior_events: List[Event],
|
|
417
|
+
) -> Tuple[List[Event], List[Event]]:
|
|
418
|
+
"""Slices the turn events when assertion order is enabled."""
|
|
419
|
+
if not step.assertion_order_enabled:
|
|
420
|
+
return turn_events, prior_events
|
|
421
|
+
|
|
422
|
+
if not matching_event:
|
|
423
|
+
return turn_events, prior_events
|
|
424
|
+
|
|
425
|
+
matching_event_index = turn_events.index(matching_event)
|
|
426
|
+
if matching_event_index + 1 < len(turn_events):
|
|
427
|
+
prior_events += turn_events[: matching_event_index + 1]
|
|
428
|
+
turn_events = turn_events[matching_event_index + 1 :]
|
|
429
|
+
|
|
430
|
+
return turn_events, prior_events
|
|
431
|
+
|
|
432
|
+
async def run_assertions(
|
|
433
|
+
self,
|
|
434
|
+
sender_id: str,
|
|
435
|
+
test_case: TestCase,
|
|
436
|
+
input_metadata: Optional[List[Metadata]],
|
|
437
|
+
) -> TestResult:
|
|
438
|
+
"""Runs the assertions defined in the test case."""
|
|
439
|
+
tracker = await self.agent.processor.get_tracker(sender_id) # type: ignore[union-attr]
|
|
440
|
+
|
|
441
|
+
assertion_failure = None
|
|
442
|
+
assertion_failure_found = False
|
|
443
|
+
input_metadata = input_metadata if input_metadata else []
|
|
444
|
+
|
|
445
|
+
for index, step in enumerate(test_case.steps):
|
|
446
|
+
if not step.assertions:
|
|
447
|
+
structlogger.debug(
|
|
448
|
+
"e2e_test_runner.run_assertions.no_assertions.skipping_step",
|
|
449
|
+
step=step,
|
|
450
|
+
)
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
additional_splitting_conditions = self._get_additional_splitting_conditions(
|
|
454
|
+
step, input_metadata, tracker, test_case
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
turn_events, prior_events = self._get_current_user_turn_and_prior_events(
|
|
458
|
+
tracker, additional_splitting_conditions, step
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
if not turn_events:
|
|
462
|
+
return TestResult(
|
|
463
|
+
pass_status=False,
|
|
464
|
+
test_case=test_case,
|
|
465
|
+
difference=[],
|
|
466
|
+
error_line=step.line,
|
|
467
|
+
assertion_failure=None,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
for assertion in step.assertions:
|
|
471
|
+
structlogger.debug(
|
|
472
|
+
"e2e_test_runner.run_assertions.running_assertion",
|
|
473
|
+
test_case_name=test_case.name,
|
|
474
|
+
step_text=step.text,
|
|
475
|
+
assertion_type=assertion.type(),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
assertion_order_error_msg = ""
|
|
479
|
+
|
|
480
|
+
if step.assertion_order_enabled:
|
|
481
|
+
assertion_order_error_msg = (
|
|
482
|
+
" You have enabled assertion order, "
|
|
483
|
+
"you should check the order in which the "
|
|
484
|
+
"assertions are listed for this user step."
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
assertion_failure, matching_event = assertion.run(
|
|
488
|
+
turn_events,
|
|
489
|
+
prior_events=prior_events,
|
|
490
|
+
assertion_order_error_message=assertion_order_error_msg,
|
|
491
|
+
llm_judge_config=self.llm_judge_config,
|
|
492
|
+
step_text=step.text,
|
|
493
|
+
step_index=index,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
if assertion_failure:
|
|
497
|
+
assertion_failure_found = True
|
|
498
|
+
structlogger.debug(
|
|
499
|
+
"e2e_test_runner.run_assertions.assertion_failure_found",
|
|
500
|
+
test_case_name=test_case.name,
|
|
501
|
+
error_line=assertion_failure.error_line,
|
|
502
|
+
)
|
|
503
|
+
break
|
|
504
|
+
|
|
505
|
+
turn_events, prior_events = self._slice_turn_events(
|
|
506
|
+
step, matching_event, turn_events, copy.deepcopy(prior_events)
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
if assertion_failure_found:
|
|
510
|
+
# don't continue with the next steps if an assertion failed
|
|
511
|
+
break
|
|
512
|
+
|
|
513
|
+
return TestResult(
|
|
514
|
+
pass_status=not assertion_failure,
|
|
515
|
+
test_case=test_case,
|
|
516
|
+
difference=[],
|
|
517
|
+
error_line=assertion_failure.error_line if assertion_failure else None,
|
|
518
|
+
assertion_failure=assertion_failure,
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
@classmethod
|
|
522
|
+
def _resolve_successful_bot_utter(
|
|
523
|
+
cls,
|
|
524
|
+
expected_result: TestStep,
|
|
525
|
+
test_response: ActualStepOutput,
|
|
526
|
+
) -> str:
|
|
527
|
+
"""Returns the diff text for a successful bot utter test step."""
|
|
528
|
+
for event in test_response.bot_uttered_events:
|
|
529
|
+
if expected_result.matches_event(event):
|
|
530
|
+
# remove the event that is already matched so
|
|
531
|
+
# that we dont compare against it again
|
|
532
|
+
test_response.remove_bot_uttered_event(event)
|
|
533
|
+
if expected_result.text is not None:
|
|
534
|
+
text = f"{expected_result.actor}: {expected_result.text}"
|
|
535
|
+
elif expected_result.template is not None:
|
|
536
|
+
text = f"{expected_result.actor}: {expected_result.template}"
|
|
537
|
+
break
|
|
538
|
+
|
|
539
|
+
return text
|
|
540
|
+
|
|
541
|
+
@classmethod
|
|
542
|
+
def _resolve_successful_set_slot(
|
|
543
|
+
cls,
|
|
544
|
+
expected_result: TestStep,
|
|
545
|
+
test_response: ActualStepOutput,
|
|
546
|
+
) -> str:
|
|
547
|
+
"""Returns the diff text for a successful set slot test step."""
|
|
548
|
+
slot_name = expected_result.get_slot_name()
|
|
549
|
+
text = f"slot_was_set: {slot_name}"
|
|
550
|
+
|
|
551
|
+
for event in test_response.slot_set_events:
|
|
552
|
+
if expected_result.matches_event(event):
|
|
553
|
+
# remove the event that is already matched so
|
|
554
|
+
# that we dont compare against it again
|
|
555
|
+
test_response.remove_slot_set_event(event)
|
|
556
|
+
if event.value is not None:
|
|
557
|
+
text += f": {event.value} ({type(event.value).__name__})"
|
|
558
|
+
break
|
|
559
|
+
|
|
560
|
+
return text
|
|
561
|
+
|
|
562
|
+
@classmethod
|
|
563
|
+
def _resolve_successful_slot_was_not_set(cls, expected_result: TestStep) -> str:
|
|
564
|
+
"""Returns the diff text for a successful slot was not set test step."""
|
|
565
|
+
slot_name = expected_result.get_slot_name()
|
|
566
|
+
slot_value = expected_result.get_slot_value()
|
|
567
|
+
text = f"slot_was_not_set: {slot_name}"
|
|
568
|
+
if expected_result.is_slot_instance_dict():
|
|
569
|
+
text += f": {slot_value}"
|
|
570
|
+
|
|
571
|
+
return text
|
|
572
|
+
|
|
573
|
+
@classmethod
|
|
574
|
+
def _find_first_non_matched_utterance(
|
|
575
|
+
cls,
|
|
576
|
+
test_response: ActualStepOutput,
|
|
577
|
+
bot_utter_test_steps: List[TestStep],
|
|
578
|
+
) -> Optional[BotUttered]:
|
|
579
|
+
"""Finds the first non matched utterance in events (if any)."""
|
|
580
|
+
bot_events = test_response.bot_uttered_events
|
|
581
|
+
matching_events = []
|
|
582
|
+
for event in bot_events:
|
|
583
|
+
for test_step in bot_utter_test_steps:
|
|
584
|
+
if test_step.matches_event(event):
|
|
585
|
+
matching_events.append(event)
|
|
586
|
+
break
|
|
587
|
+
not_matching_events = [
|
|
588
|
+
event for event in bot_events if event not in matching_events
|
|
589
|
+
]
|
|
590
|
+
return not_matching_events[0] if not_matching_events else None
|
|
591
|
+
|
|
592
|
+
@classmethod
|
|
593
|
+
def _handle_fail_diff(
|
|
594
|
+
cls,
|
|
595
|
+
failed_step: TestStep,
|
|
596
|
+
test_response: ActualStepOutput,
|
|
597
|
+
bot_utter_test_steps: List[TestStep],
|
|
598
|
+
) -> Tuple[str, str]:
|
|
599
|
+
"""Handles generating the diff text for a failed test step."""
|
|
600
|
+
if failed_step.text is not None:
|
|
601
|
+
diff_test_text = f"{failed_step.actor}: {failed_step.text}"
|
|
602
|
+
diff_actual_text = NO_RESPONSE
|
|
603
|
+
event: Optional[Union[BotUttered, SlotSet]] = (
|
|
604
|
+
cls._find_first_non_matched_utterance(
|
|
605
|
+
test_response, bot_utter_test_steps
|
|
606
|
+
)
|
|
607
|
+
)
|
|
608
|
+
if event and isinstance(event, BotUttered):
|
|
609
|
+
test_response.remove_bot_uttered_event(event)
|
|
610
|
+
diff_actual_text = f"bot: {event.text}"
|
|
611
|
+
|
|
612
|
+
elif failed_step.template is not None:
|
|
613
|
+
diff_test_text = f"{failed_step.actor}: {failed_step.template}"
|
|
614
|
+
diff_actual_text = NO_RESPONSE
|
|
615
|
+
event = cls._find_first_non_matched_utterance(
|
|
616
|
+
test_response, bot_utter_test_steps
|
|
617
|
+
)
|
|
618
|
+
if event:
|
|
619
|
+
test_response.remove_bot_uttered_event(event)
|
|
620
|
+
diff_actual_text = f"bot: {event.metadata.get('utter_action')}"
|
|
621
|
+
|
|
622
|
+
elif failed_step.slot_was_set:
|
|
623
|
+
slot_name = failed_step.get_slot_name()
|
|
624
|
+
|
|
625
|
+
diff_test_text = f"slot_was_set: {slot_name}"
|
|
626
|
+
if failed_step.is_slot_instance_dict():
|
|
627
|
+
slot_value = failed_step.get_slot_value()
|
|
628
|
+
diff_test_text += f": {slot_value} ({type(slot_value).__name__})"
|
|
629
|
+
|
|
630
|
+
diff_actual_text = NO_SLOT
|
|
631
|
+
for event in test_response.slot_set_events:
|
|
632
|
+
if slot_name == event.key:
|
|
633
|
+
diff_actual_text = (
|
|
634
|
+
f"slot_was_set: {event.key}: {event.value} "
|
|
635
|
+
f"({type(event.value).__name__})"
|
|
636
|
+
)
|
|
637
|
+
test_response.remove_slot_set_event(event)
|
|
638
|
+
break
|
|
639
|
+
|
|
640
|
+
elif failed_step.slot_was_not_set:
|
|
641
|
+
slot_name = failed_step.get_slot_name()
|
|
642
|
+
|
|
643
|
+
diff_test_text = f"slot_was_not_set: {slot_name}"
|
|
644
|
+
if failed_step.is_slot_instance_dict():
|
|
645
|
+
slot_value = failed_step.get_slot_value()
|
|
646
|
+
diff_test_text += f": {slot_value}"
|
|
647
|
+
|
|
648
|
+
for event in test_response.slot_set_events:
|
|
649
|
+
if slot_name == event.key:
|
|
650
|
+
diff_actual_text = (
|
|
651
|
+
f"slot_was_set: {event.key}: {event.value} "
|
|
652
|
+
f"({type(event.value).__name__})"
|
|
653
|
+
)
|
|
654
|
+
test_response.remove_slot_set_event(event)
|
|
655
|
+
break
|
|
656
|
+
|
|
657
|
+
return diff_test_text, diff_actual_text
|
|
658
|
+
|
|
659
|
+
@classmethod
|
|
660
|
+
def _select_bot_utter_turns(
|
|
661
|
+
cls,
|
|
662
|
+
test_turns: TEST_TURNS_TYPE,
|
|
663
|
+
start_index: int,
|
|
664
|
+
) -> List[TestStep]:
|
|
665
|
+
"""Selects the TestSteps from the test turns that match BotUttered events."""
|
|
666
|
+
bot_utter_turns = []
|
|
667
|
+
for index in range(start_index, len(test_turns) - 1):
|
|
668
|
+
test_turn = test_turns[index]
|
|
669
|
+
if isinstance(test_turn, TestStep):
|
|
670
|
+
if test_turn.text is not None or test_turn.template is not None:
|
|
671
|
+
bot_utter_turns.append(test_turn)
|
|
672
|
+
elif isinstance(test_turn, ActualStepOutput):
|
|
673
|
+
break
|
|
674
|
+
|
|
675
|
+
return bot_utter_turns
|
|
676
|
+
|
|
677
|
+
@classmethod
|
|
678
|
+
def human_readable_diff(
|
|
679
|
+
cls,
|
|
680
|
+
test_turns: TEST_TURNS_TYPE,
|
|
681
|
+
fail_positions: List[Tuple[TestFailure, int]],
|
|
682
|
+
) -> List[str]:
|
|
683
|
+
"""Returns a human readable diff of the test case and the actual conversation.
|
|
684
|
+
|
|
685
|
+
Given an ordered list of test steps and actual conversation events, this
|
|
686
|
+
method will return a human readable diff of the two.
|
|
687
|
+
The diff uses difflib to compare the two lists and will highlight
|
|
688
|
+
differences between the two in a pytest style diff.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
test_turns: The transcript of test cases and events.
|
|
692
|
+
fail_positions: The positions of the test failures.
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
The human readable diff.
|
|
696
|
+
"""
|
|
697
|
+
actual_transcript = []
|
|
698
|
+
expected_transcript = []
|
|
699
|
+
failure_points = {position for _, position in fail_positions}
|
|
700
|
+
# This will only be used when the TestCase is not started
|
|
701
|
+
# with a user step
|
|
702
|
+
latest_response: ActualStepOutput = test_turns[-1] # type: ignore[assignment]
|
|
703
|
+
|
|
704
|
+
for index in range(max(failure_points) + 1):
|
|
705
|
+
test_result = test_turns[index]
|
|
706
|
+
if index in failure_points:
|
|
707
|
+
diff_test_text, diff_actual_text = cls._handle_fail_diff(
|
|
708
|
+
test_result, # type: ignore[arg-type]
|
|
709
|
+
latest_response,
|
|
710
|
+
cls._select_bot_utter_turns(test_turns, index),
|
|
711
|
+
) # test_result can only be TestStep in failure_points
|
|
712
|
+
actual_transcript.append(diff_actual_text)
|
|
713
|
+
expected_transcript.append(diff_test_text)
|
|
714
|
+
continue
|
|
715
|
+
|
|
716
|
+
if isinstance(test_result, TestStep):
|
|
717
|
+
if test_result.text is not None or test_result.template is not None:
|
|
718
|
+
diff_test_text = cls._resolve_successful_bot_utter(
|
|
719
|
+
expected_result=test_result,
|
|
720
|
+
test_response=latest_response,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
if test_result.slot_was_set:
|
|
724
|
+
diff_test_text = cls._resolve_successful_set_slot(
|
|
725
|
+
expected_result=test_result,
|
|
726
|
+
test_response=latest_response,
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
if test_result.slot_was_not_set:
|
|
730
|
+
diff_test_text = cls._resolve_successful_slot_was_not_set(
|
|
731
|
+
expected_result=test_result,
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
elif isinstance(test_result, ActualStepOutput):
|
|
735
|
+
latest_response = test_result
|
|
736
|
+
event = test_result.get_user_uttered_event()
|
|
737
|
+
if event:
|
|
738
|
+
diff_test_text = f"{test_result.actor}: {event.text}"
|
|
739
|
+
else:
|
|
740
|
+
raise RasaException(
|
|
741
|
+
f"Bot did not catch user "
|
|
742
|
+
f"event: {test_result.actor}: {test_result.text}."
|
|
743
|
+
)
|
|
744
|
+
else:
|
|
745
|
+
raise ValueError(f"Unexpected test result type: {type(test_result)}")
|
|
746
|
+
|
|
747
|
+
# test passed in these cases so it's the same
|
|
748
|
+
actual_transcript.append(diff_test_text)
|
|
749
|
+
expected_transcript.append(diff_test_text)
|
|
750
|
+
|
|
751
|
+
return list(difflib.ndiff(actual_transcript, expected_transcript))
|
|
752
|
+
|
|
753
|
+
@classmethod
|
|
754
|
+
def find_test_failures(
|
|
755
|
+
cls,
|
|
756
|
+
test_turns: TEST_TURNS_TYPE,
|
|
757
|
+
test_case: TestCase,
|
|
758
|
+
) -> List[Tuple[TestFailure, int]]:
|
|
759
|
+
"""Finds the test failures in the transcript.
|
|
760
|
+
|
|
761
|
+
Args:
|
|
762
|
+
test_turns: The transcript of test cases and events.
|
|
763
|
+
test_case: The test case.
|
|
764
|
+
|
|
765
|
+
Returns:
|
|
766
|
+
The test failures or an empty list if there is no test failure.
|
|
767
|
+
"""
|
|
768
|
+
# This will only be used when the TestCase is not started
|
|
769
|
+
# with a user step
|
|
770
|
+
latest_response: ActualStepOutput = test_turns[-1] # type: ignore[assignment]
|
|
771
|
+
failures = []
|
|
772
|
+
position = 0
|
|
773
|
+
match = None
|
|
774
|
+
for position in range(len(test_turns) - 1):
|
|
775
|
+
turn_value = test_turns[position]
|
|
776
|
+
if isinstance(turn_value, ActualStepOutput):
|
|
777
|
+
latest_response = turn_value
|
|
778
|
+
elif isinstance(turn_value, TestStep):
|
|
779
|
+
if turn_value.text is not None or turn_value.template is not None:
|
|
780
|
+
match = cls._does_match_exist(
|
|
781
|
+
latest_response.bot_uttered_events, turn_value
|
|
782
|
+
)
|
|
783
|
+
if turn_value.slot_was_set:
|
|
784
|
+
match = cls._does_match_exist(
|
|
785
|
+
latest_response.slot_set_events, turn_value
|
|
786
|
+
)
|
|
787
|
+
if turn_value.slot_was_not_set:
|
|
788
|
+
match = not cls._does_match_exist(
|
|
789
|
+
latest_response.slot_set_events, turn_value
|
|
790
|
+
)
|
|
791
|
+
if not match:
|
|
792
|
+
failures.append((TestFailure(test_case, turn_value.line), position))
|
|
793
|
+
else:
|
|
794
|
+
raise ValueError(f"Unexpected turn value type: {type(turn_value)}")
|
|
795
|
+
|
|
796
|
+
return failures
|
|
797
|
+
|
|
798
|
+
@classmethod
|
|
799
|
+
def _does_match_exist(
|
|
800
|
+
cls,
|
|
801
|
+
actual_events: Optional[List[Union[UserUttered, BotUttered, SlotSet]]],
|
|
802
|
+
expected: TestStep,
|
|
803
|
+
) -> bool:
|
|
804
|
+
if not actual_events:
|
|
805
|
+
return False
|
|
806
|
+
|
|
807
|
+
match = False
|
|
808
|
+
for event in actual_events:
|
|
809
|
+
if expected.matches_event(event):
|
|
810
|
+
return True
|
|
811
|
+
return match
|
|
812
|
+
|
|
813
|
+
async def set_up_fixtures(
|
|
814
|
+
self,
|
|
815
|
+
fixtures: List[Fixture],
|
|
816
|
+
sender_id: Text,
|
|
817
|
+
) -> None:
|
|
818
|
+
"""Sets slots in the tracker as defined by the input fixtures.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
fixtures: List of `Fixture` objects.
|
|
822
|
+
sender_id: The conversation id.
|
|
823
|
+
"""
|
|
824
|
+
if not fixtures:
|
|
825
|
+
return
|
|
826
|
+
if not self.agent.processor:
|
|
827
|
+
return
|
|
828
|
+
|
|
829
|
+
tracker = await self.agent.processor.fetch_tracker_with_initial_session(
|
|
830
|
+
sender_id, output_channel=CollectingOutputChannel()
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
for fixture in fixtures:
|
|
834
|
+
for slot_name, slot_value in fixture.slots_set.items():
|
|
835
|
+
tracker.update(SlotSet(slot_name, slot_value))
|
|
836
|
+
|
|
837
|
+
await self.agent.tracker_store.save(tracker)
|
|
838
|
+
|
|
839
|
+
@staticmethod
|
|
840
|
+
def filter_fixtures_for_test_case(
|
|
841
|
+
test_case: TestCase, fixtures: List[Fixture]
|
|
842
|
+
) -> List[Fixture]:
|
|
843
|
+
"""Filters the input fixtures for the input test case.
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
test_case: The test case.
|
|
847
|
+
fixtures: The fixtures.
|
|
848
|
+
|
|
849
|
+
Returns:
|
|
850
|
+
The filtered fixtures.
|
|
851
|
+
"""
|
|
852
|
+
return list(
|
|
853
|
+
filter(
|
|
854
|
+
lambda fixture: test_case.fixture_names
|
|
855
|
+
and fixture.name in test_case.fixture_names,
|
|
856
|
+
fixtures,
|
|
857
|
+
)
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
@staticmethod
|
|
861
|
+
def filter_metadata_for_input(
|
|
862
|
+
metadata_name: Optional[Text], test_suite_metadata: List[Metadata]
|
|
863
|
+
) -> Optional[Metadata]:
|
|
864
|
+
"""Filters the test suite metadata for a metadata name.
|
|
865
|
+
|
|
866
|
+
Args:
|
|
867
|
+
metadata_name: The test case or user step metadata name.
|
|
868
|
+
test_suite_metadata: The top level list of all metadata definitions.
|
|
869
|
+
|
|
870
|
+
Returns:
|
|
871
|
+
The filtered metadata.
|
|
872
|
+
"""
|
|
873
|
+
if not metadata_name:
|
|
874
|
+
return None
|
|
875
|
+
|
|
876
|
+
filtered_metadata = list(
|
|
877
|
+
filter(
|
|
878
|
+
lambda metadata: metadata_name and metadata.name == metadata_name,
|
|
879
|
+
test_suite_metadata,
|
|
880
|
+
)
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
if not filtered_metadata:
|
|
884
|
+
structlogger.warning(
|
|
885
|
+
"e2e_test_runner.filter_metadata_for_input",
|
|
886
|
+
message=f"Metadata '{metadata_name}' is not defined in the input "
|
|
887
|
+
f"metadata.",
|
|
888
|
+
)
|
|
889
|
+
return None
|
|
890
|
+
|
|
891
|
+
return filtered_metadata[0]
|
|
892
|
+
|
|
893
|
+
async def run_tests(
|
|
894
|
+
self,
|
|
895
|
+
input_test_cases: List[TestCase],
|
|
896
|
+
input_fixtures: List[Fixture],
|
|
897
|
+
fail_fast: bool = False,
|
|
898
|
+
**kwargs: Any,
|
|
899
|
+
) -> List["TestResult"]:
|
|
900
|
+
"""Runs the test cases.
|
|
901
|
+
|
|
902
|
+
Args:
|
|
903
|
+
input_test_cases: Input test cases.
|
|
904
|
+
input_fixtures: Input fixtures.
|
|
905
|
+
fail_fast: Whether to fail fast.
|
|
906
|
+
**kwargs: Additional arguments which are passed here.
|
|
907
|
+
|
|
908
|
+
Returns:
|
|
909
|
+
List of test results.
|
|
910
|
+
"""
|
|
911
|
+
results = []
|
|
912
|
+
input_metadata = kwargs.get("input_metadata", None)
|
|
913
|
+
|
|
914
|
+
# telemetry call for tracking test runs
|
|
915
|
+
track_e2e_test_run(input_test_cases, input_fixtures, input_metadata)
|
|
916
|
+
|
|
917
|
+
for test_case in input_test_cases:
|
|
918
|
+
test_case_name = test_case.name.replace(" ", "_")
|
|
919
|
+
# Add the name of the file and the current test case name being
|
|
920
|
+
# executed in order to properly retrieve stub custom action
|
|
921
|
+
if self.agent.endpoints and self.agent.endpoints.action:
|
|
922
|
+
self.agent.endpoints.action.kwargs[TEST_FILE_NAME] = Path(
|
|
923
|
+
test_case.file
|
|
924
|
+
).name
|
|
925
|
+
self.agent.endpoints.action.kwargs[TEST_CASE_NAME] = test_case_name
|
|
926
|
+
|
|
927
|
+
# add timestamp suffix to ensure sender_id is unique
|
|
928
|
+
sender_id = f"{test_case_name}_{datetime.datetime.now()}"
|
|
929
|
+
test_turns = await self._run_test_case(
|
|
930
|
+
sender_id, input_fixtures, input_metadata, test_case
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
if not test_case.uses_assertions():
|
|
934
|
+
test_result = self.generate_test_result(test_turns, test_case)
|
|
935
|
+
else:
|
|
936
|
+
test_result = await self.run_assertions(
|
|
937
|
+
sender_id, test_case, input_metadata
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
results.append(test_result)
|
|
941
|
+
|
|
942
|
+
coverage = kwargs.get("coverage", False)
|
|
943
|
+
if coverage:
|
|
944
|
+
tracker = await self.agent.tracker_store.retrieve(sender_id)
|
|
945
|
+
if tracker:
|
|
946
|
+
test_result.tested_paths, test_result.tested_commands = (
|
|
947
|
+
self._get_tested_flow_paths_and_commands(
|
|
948
|
+
tracker.events, test_result
|
|
949
|
+
)
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
if fail_fast and not test_result.pass_status:
|
|
953
|
+
break
|
|
954
|
+
|
|
955
|
+
return results
|
|
956
|
+
|
|
957
|
+
async def _run_test_case(
|
|
958
|
+
self,
|
|
959
|
+
sender_id: str,
|
|
960
|
+
input_fixtures: List[Fixture],
|
|
961
|
+
input_metadata: Optional[List[Metadata]],
|
|
962
|
+
test_case: TestCase,
|
|
963
|
+
) -> TEST_TURNS_TYPE:
|
|
964
|
+
collector = CollectingOutputChannel()
|
|
965
|
+
|
|
966
|
+
if input_fixtures:
|
|
967
|
+
test_fixtures = self.filter_fixtures_for_test_case(
|
|
968
|
+
test_case, input_fixtures
|
|
969
|
+
)
|
|
970
|
+
await self.set_up_fixtures(test_fixtures, sender_id)
|
|
971
|
+
|
|
972
|
+
test_case_metadata = None
|
|
973
|
+
if input_metadata:
|
|
974
|
+
test_case_metadata = self.filter_metadata_for_input(
|
|
975
|
+
test_case.metadata_name, input_metadata
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
return await self.run_prediction_loop(
|
|
979
|
+
collector,
|
|
980
|
+
test_case.steps,
|
|
981
|
+
sender_id,
|
|
982
|
+
test_case_metadata,
|
|
983
|
+
input_metadata,
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
async def run_tests_for_fine_tuning(
|
|
987
|
+
self,
|
|
988
|
+
input_test_cases: List[TestCase],
|
|
989
|
+
input_fixtures: List[Fixture],
|
|
990
|
+
input_metadata: Optional[List[Metadata]],
|
|
991
|
+
) -> List[Conversation]:
|
|
992
|
+
"""Runs the test cases for fine-tuning.
|
|
993
|
+
|
|
994
|
+
Converts passing test cases into conversation objects containing the
|
|
995
|
+
prompts and llm commands per user message.
|
|
996
|
+
|
|
997
|
+
Args:
|
|
998
|
+
input_test_cases: Input test cases.
|
|
999
|
+
input_fixtures: Input fixtures.
|
|
1000
|
+
input_metadata: Input metadata.
|
|
1001
|
+
|
|
1002
|
+
Returns:
|
|
1003
|
+
List of conversations.
|
|
1004
|
+
"""
|
|
1005
|
+
import rasa.llm_fine_tuning.annotation_module
|
|
1006
|
+
|
|
1007
|
+
conversations = []
|
|
1008
|
+
|
|
1009
|
+
for i in tqdm(range(len(input_test_cases))):
|
|
1010
|
+
test_case = input_test_cases[i]
|
|
1011
|
+
# add timestamp suffix to ensure sender_id is unique
|
|
1012
|
+
sender_id = f"{test_case.name}_{datetime.datetime.now()}"
|
|
1013
|
+
test_turns = await self._run_test_case(
|
|
1014
|
+
sender_id, input_fixtures, input_metadata, test_case
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
# check if the e2e test is passing, only convert passing e2e tests into
|
|
1018
|
+
# conversations
|
|
1019
|
+
if not test_case.uses_assertions():
|
|
1020
|
+
test_result = self.generate_test_result(test_turns, test_case)
|
|
1021
|
+
else:
|
|
1022
|
+
test_result = await self.run_assertions(
|
|
1023
|
+
sender_id, test_case, input_metadata
|
|
1024
|
+
)
|
|
1025
|
+
if not test_result.pass_status:
|
|
1026
|
+
structlogger.warning(
|
|
1027
|
+
"annotation_module.skip_test_case.failing_e2e_test",
|
|
1028
|
+
test_case=test_case.name,
|
|
1029
|
+
file=test_case.file,
|
|
1030
|
+
)
|
|
1031
|
+
continue
|
|
1032
|
+
|
|
1033
|
+
tracker = await self.agent.tracker_store.retrieve(sender_id)
|
|
1034
|
+
conversation = rasa.llm_fine_tuning.annotation_module.generate_conversation(
|
|
1035
|
+
test_turns, test_case, tracker, test_case.uses_assertions()
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
if conversation:
|
|
1039
|
+
conversations.append(conversation)
|
|
1040
|
+
|
|
1041
|
+
return conversations
|
|
1042
|
+
|
|
1043
|
+
@staticmethod
|
|
1044
|
+
def _action_server_is_reachable(
|
|
1045
|
+
endpoints: AvailableEndpoints, module: str = "e2e_test_runner"
|
|
1046
|
+
) -> None:
|
|
1047
|
+
"""Calls the action server health endpoint."""
|
|
1048
|
+
if not endpoints.action:
|
|
1049
|
+
structlogger.debug(
|
|
1050
|
+
f"{module}._action_server_is_reachable",
|
|
1051
|
+
message="No action endpoint configured. Skipping the health check "
|
|
1052
|
+
"of the action server.",
|
|
1053
|
+
)
|
|
1054
|
+
return
|
|
1055
|
+
|
|
1056
|
+
if endpoints.action.actions_module:
|
|
1057
|
+
structlogger.debug(
|
|
1058
|
+
f"{module}._action_server_is_reachable",
|
|
1059
|
+
message="Rasa server is configured to run custom actions directly. "
|
|
1060
|
+
"Skipping the health check of the action server.",
|
|
1061
|
+
)
|
|
1062
|
+
return
|
|
1063
|
+
|
|
1064
|
+
if not endpoints.action.url:
|
|
1065
|
+
structlogger.debug(
|
|
1066
|
+
f"{module}._action_server_is_reachable",
|
|
1067
|
+
message="Action endpoint URL is not defined in the endpoint "
|
|
1068
|
+
"configuration.",
|
|
1069
|
+
)
|
|
1070
|
+
return
|
|
1071
|
+
|
|
1072
|
+
structlogger.debug(
|
|
1073
|
+
f"{module}._action_server_is_reachable",
|
|
1074
|
+
message="Detected action URL in the endpoint configuration.\n"
|
|
1075
|
+
f"Action Server URL: {endpoints.action.url}\n"
|
|
1076
|
+
"Sending a health request to the action endpoint.",
|
|
1077
|
+
)
|
|
1078
|
+
url = urlparse(endpoints.action.url)
|
|
1079
|
+
# replace /<path> with just /health
|
|
1080
|
+
url = url._replace(path="/health").geturl() # type: ignore[assignment]
|
|
1081
|
+
try:
|
|
1082
|
+
response = requests.get(url, timeout=3)
|
|
1083
|
+
except requests.exceptions.ConnectionError as error:
|
|
1084
|
+
raise RasaException(
|
|
1085
|
+
"Action endpoint could not be reached. "
|
|
1086
|
+
"Actions server URL is defined in your endpoint configuration as "
|
|
1087
|
+
f"'{endpoints.action.url}'.\n"
|
|
1088
|
+
"Please make sure your action server is running and properly "
|
|
1089
|
+
"configured. Since running tests without a action server may "
|
|
1090
|
+
f"lead to unpredictable results.\n{error}"
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
if response.status_code != 200:
|
|
1094
|
+
raise RasaException(
|
|
1095
|
+
"Action endpoint is responding, but health status responded with "
|
|
1096
|
+
f"code {response.status_code}. Make sure your action server"
|
|
1097
|
+
" is properly configured and that the '/health' endpoint is available."
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
structlogger.debug(
|
|
1101
|
+
f"{module}._action_server_is_reachable",
|
|
1102
|
+
message="Action endpoint has responded successfully.\n"
|
|
1103
|
+
f"Response message: {response.text}\n"
|
|
1104
|
+
f"Response status code: {response.status_code}.",
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
def _get_tested_flow_paths_and_commands(
|
|
1108
|
+
self, events: List[Event], test_result: TestResult
|
|
1109
|
+
) -> Tuple[Optional[List[FlowPath]], Dict[str, Dict[str, int]]]:
|
|
1110
|
+
"""Extract tested paths and commands from dialog events.
|
|
1111
|
+
|
|
1112
|
+
A flow path consists of bot utterances and custom actions.
|
|
1113
|
+
|
|
1114
|
+
Args:
|
|
1115
|
+
events: The list of dialog events.
|
|
1116
|
+
test_result: The result of the test incl. the pass status.
|
|
1117
|
+
|
|
1118
|
+
Returns:
|
|
1119
|
+
Tuple[flow_paths: Optional[List[FlowPath]], tested_commands:
|
|
1120
|
+
Dict[str, Dict[str, int]]], where tested_commands is a
|
|
1121
|
+
dictionary like
|
|
1122
|
+
{"flow1": {"set slot": 5, "clarify": 1}, "flow2": {"set slot": 3}}
|
|
1123
|
+
"""
|
|
1124
|
+
tested_paths = []
|
|
1125
|
+
# we want to create a flow path per flow the e2e test covers
|
|
1126
|
+
# as an e2e test can cover multiple flows, we might end up creating
|
|
1127
|
+
# multiple flow paths
|
|
1128
|
+
_tested_commands: DefaultDict[str, DefaultDict[str, int]] = defaultdict(
|
|
1129
|
+
lambda: defaultdict(int)
|
|
1130
|
+
)
|
|
1131
|
+
flow_paths_stack = []
|
|
1132
|
+
|
|
1133
|
+
for event in events:
|
|
1134
|
+
if isinstance(event, FlowStarted) and not event.flow_id.startswith(
|
|
1135
|
+
RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
1136
|
+
):
|
|
1137
|
+
flow_paths_stack.append(FlowPath(event.flow_id))
|
|
1138
|
+
|
|
1139
|
+
elif (
|
|
1140
|
+
isinstance(event, FlowCompleted)
|
|
1141
|
+
and len(flow_paths_stack) > 0
|
|
1142
|
+
and event.flow_id == flow_paths_stack[-1].flow
|
|
1143
|
+
):
|
|
1144
|
+
# flow path is completed as the flow ended
|
|
1145
|
+
tested_paths.append(flow_paths_stack.pop())
|
|
1146
|
+
|
|
1147
|
+
elif isinstance(event, BotUttered):
|
|
1148
|
+
if (
|
|
1149
|
+
flow_paths_stack
|
|
1150
|
+
and STEP_ID_METADATA_KEY in event.metadata
|
|
1151
|
+
and ACTIVE_FLOW_METADATA_KEY in event.metadata
|
|
1152
|
+
):
|
|
1153
|
+
flow_paths_stack[-1].nodes.append(self._create_path_node(event))
|
|
1154
|
+
|
|
1155
|
+
elif isinstance(event, ActionExecuted):
|
|
1156
|
+
# we are only interested in custom actions
|
|
1157
|
+
if (
|
|
1158
|
+
flow_paths_stack
|
|
1159
|
+
and self.agent.domain
|
|
1160
|
+
and self.agent.domain.is_custom_action(event.action_name)
|
|
1161
|
+
and STEP_ID_METADATA_KEY in event.metadata
|
|
1162
|
+
and ACTIVE_FLOW_METADATA_KEY in event.metadata
|
|
1163
|
+
):
|
|
1164
|
+
flow_paths_stack[-1].nodes.append(self._create_path_node(event))
|
|
1165
|
+
|
|
1166
|
+
# Time to gather tested commands
|
|
1167
|
+
elif isinstance(event, UserUttered):
|
|
1168
|
+
if event.parse_data and COMMANDS in event.parse_data:
|
|
1169
|
+
commands = [
|
|
1170
|
+
command["command"] for command in event.parse_data[COMMANDS]
|
|
1171
|
+
]
|
|
1172
|
+
current_flow = (
|
|
1173
|
+
flow_paths_stack[-1].flow if flow_paths_stack else "no_flow"
|
|
1174
|
+
)
|
|
1175
|
+
for command in commands:
|
|
1176
|
+
_tested_commands[current_flow][command] += 1
|
|
1177
|
+
|
|
1178
|
+
# It might be that an e2e test stops before a flow was completed.
|
|
1179
|
+
# Add the remaining flow paths to the tested paths list.
|
|
1180
|
+
while len(flow_paths_stack) > 0:
|
|
1181
|
+
tested_paths.append(flow_paths_stack.pop())
|
|
1182
|
+
|
|
1183
|
+
# Convert _tested_commands to normal dicts
|
|
1184
|
+
tested_commands = {key: dict(value) for key, value in _tested_commands.items()} # type: Dict[str, Dict[str, int]]
|
|
1185
|
+
|
|
1186
|
+
return tested_paths, tested_commands
|
|
1187
|
+
|
|
1188
|
+
@staticmethod
|
|
1189
|
+
def _create_path_node(event: Event) -> PathNode:
|
|
1190
|
+
flow_id = event.metadata[ACTIVE_FLOW_METADATA_KEY]
|
|
1191
|
+
step_id = event.metadata[STEP_ID_METADATA_KEY]
|
|
1192
|
+
return PathNode(step_id=step_id, flow=flow_id)
|