rasa-pro 3.13.1a18__py3-none-any.whl → 3.13.1a20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- rasa/__main__.py +8 -0
- rasa/builder/auth.py +71 -0
- rasa/builder/config.py +16 -0
- rasa/builder/copilot/constants.py +15 -0
- rasa/builder/copilot/copilot.py +342 -0
- rasa/builder/copilot/copilot_response_handler.py +471 -0
- rasa/builder/copilot/exceptions.py +20 -0
- rasa/builder/copilot/models.py +344 -0
- rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +495 -0
- rasa/builder/copilot/telemetry.py +195 -0
- rasa/builder/document_retrieval/__init__.py +0 -0
- rasa/builder/document_retrieval/constants.py +16 -0
- rasa/builder/{inkeep_document_retrieval.py → document_retrieval/inkeep_document_retrieval.py} +53 -44
- rasa/builder/document_retrieval/models.py +62 -0
- rasa/builder/download.py +140 -0
- rasa/builder/guardrails/__init__.py +1 -0
- rasa/builder/guardrails/constants.py +4 -0
- rasa/builder/guardrails/exceptions.py +4 -0
- rasa/builder/guardrails/lakera.py +188 -0
- rasa/builder/guardrails/models.py +199 -0
- rasa/builder/guardrails/utils.py +305 -0
- rasa/builder/job_manager.py +87 -0
- rasa/builder/jobs.py +232 -0
- rasa/builder/llm_service.py +89 -173
- rasa/builder/logging_utils.py +162 -4
- rasa/builder/main.py +29 -16
- rasa/builder/models.py +93 -121
- rasa/builder/project_generator.py +91 -7
- rasa/builder/scrape_rasa_docs.py +1 -1
- rasa/builder/service.py +650 -452
- rasa/builder/shared/tracker_context.py +212 -0
- rasa/builder/validation_service.py +4 -4
- rasa/cli/data.py +8 -3
- rasa/cli/project_templates/basic/actions/action_api.py +15 -0
- rasa/cli/project_templates/basic/actions/action_human_handoff.py +44 -0
- rasa/cli/project_templates/basic/config.yml +23 -0
- rasa/cli/project_templates/{plain → basic}/credentials.yml +8 -7
- rasa/cli/project_templates/basic/data/general/feedback.yml +20 -0
- rasa/cli/project_templates/basic/data/general/goodbye.yml +6 -0
- rasa/cli/project_templates/basic/data/general/hello.yml +7 -0
- rasa/cli/project_templates/basic/data/general/help.yml +6 -0
- rasa/cli/project_templates/basic/data/general/human_handoff.yml +16 -0
- rasa/cli/project_templates/basic/data/general/welcome.yml +9 -0
- rasa/cli/project_templates/{finance/data/patterns → basic/data/system}/pattern_completed.yml +2 -1
- rasa/cli/project_templates/basic/data/system/pattern_correction.yml +7 -0
- rasa/cli/project_templates/basic/data/system/pattern_search.yml +8 -0
- rasa/cli/project_templates/basic/data/system/pattern_session_start.yml +8 -0
- rasa/cli/project_templates/basic/docs/rasa_assistant_qa.txt +65 -0
- rasa/cli/project_templates/basic/docs/template.txt +7 -0
- rasa/cli/project_templates/basic/domain/general/assistant_details.yml +12 -0
- rasa/cli/project_templates/basic/domain/general/bot_identity.yml +5 -0
- rasa/cli/project_templates/basic/domain/general/cannot_handle.yml +5 -0
- rasa/cli/project_templates/basic/domain/general/feedback.yml +28 -0
- rasa/cli/project_templates/basic/domain/general/goodbye.yml +7 -0
- rasa/cli/project_templates/basic/domain/general/help.yml +5 -0
- rasa/cli/project_templates/basic/domain/general/human_handoff_domain.yml +35 -0
- rasa/cli/project_templates/{finance/domain/default_actions.yml → basic/domain/general/utils.yml} +0 -3
- rasa/cli/project_templates/basic/domain/general/welcome.yml +7 -0
- rasa/cli/project_templates/{plain → basic}/endpoints.yml +42 -27
- rasa/cli/project_templates/basic/prompts/rephraser_demo_personality_prompt.jinja2 +19 -0
- rasa/cli/project_templates/defaults.py +25 -3
- rasa/cli/project_templates/finance/actions/__init__.py +46 -0
- rasa/cli/project_templates/finance/actions/accounts/__init__.py +0 -0
- rasa/cli/project_templates/finance/actions/{action_ask_account.py → accounts/action_ask_account.py} +6 -9
- rasa/cli/project_templates/finance/actions/{action_check_balance.py → accounts/action_check_balance.py} +4 -4
- rasa/cli/project_templates/finance/actions/action_session_start.py +11 -6
- rasa/cli/project_templates/finance/actions/cards/__init__.py +0 -0
- rasa/cli/project_templates/finance/actions/{action_ask_card.py → cards/action_ask_card.py} +4 -3
- rasa/cli/project_templates/finance/actions/{action_check_card_existence.py → cards/action_check_card_existence.py} +4 -3
- rasa/cli/project_templates/finance/actions/{action_update_card_status.py → cards/action_update_card_status.py} +18 -9
- rasa/cli/project_templates/finance/actions/database.py +1 -0
- rasa/cli/project_templates/finance/actions/transfers/__init__.py +0 -0
- rasa/cli/project_templates/finance/actions/{action_add_payee.py → transfers/action_add_payee.py} +8 -3
- rasa/cli/project_templates/finance/actions/{action_ask_account_from.py → transfers/action_ask_account_from.py} +5 -4
- rasa/cli/project_templates/finance/actions/{action_check_payee_existence.py → transfers/action_check_payee_existence.py} +3 -3
- rasa/cli/project_templates/finance/actions/{action_check_sufficient_funds.py → transfers/action_check_sufficient_funds.py} +3 -4
- rasa/cli/project_templates/finance/actions/{action_list_payees.py → transfers/action_list_payees.py} +4 -3
- rasa/cli/project_templates/finance/actions/{action_remove_payee.py → transfers/action_remove_payee.py} +4 -4
- rasa/cli/project_templates/finance/config.yml +8 -19
- rasa/cli/project_templates/finance/credentials.yml +6 -7
- rasa/cli/project_templates/finance/csvs/cards.csv +10 -10
- rasa/cli/project_templates/finance/csvs/payees.csv +10 -9
- rasa/cli/project_templates/finance/data/{flows → accounts}/check_balance.yml +2 -1
- rasa/cli/project_templates/finance/data/general/bot_identity.yml +6 -0
- rasa/cli/project_templates/finance/data/general/feedback.yml +20 -0
- rasa/cli/project_templates/finance/data/general/goodbye.yml +6 -0
- rasa/cli/project_templates/finance/data/general/hello.yml +7 -0
- rasa/cli/project_templates/finance/data/{flows/welcome.yml → general/help.yml} +2 -7
- rasa/cli/project_templates/finance/data/general/human_handoff.yml +16 -0
- rasa/cli/project_templates/finance/data/general/welcome.yml +9 -0
- rasa/cli/project_templates/finance/data/{patterns → system/patterns}/pattern_chitchat.yml +0 -2
- rasa/cli/project_templates/finance/data/system/patterns/pattern_completed.yml +7 -0
- rasa/cli/project_templates/finance/data/system/patterns/pattern_correction.yml +7 -0
- rasa/cli/project_templates/finance/data/system/patterns/pattern_search.yml +8 -0
- rasa/cli/project_templates/finance/data/{patterns → system/patterns}/pattern_session_start.yml +0 -1
- rasa/cli/project_templates/finance/domain/{check_balance.yml → accounts/check_balance.yml} +2 -0
- rasa/cli/project_templates/finance/domain/general/assistant_details.yml +12 -0
- rasa/cli/project_templates/finance/domain/general/bot_identity.yml +5 -0
- rasa/cli/project_templates/finance/domain/general/cannot_handle.yml +5 -0
- rasa/cli/project_templates/finance/domain/general/defaults.yml +24 -0
- rasa/cli/project_templates/finance/domain/general/feedback.yml +28 -0
- rasa/cli/project_templates/finance/domain/general/goodbye.yml +7 -0
- rasa/cli/project_templates/finance/domain/general/help.yml +5 -0
- rasa/cli/project_templates/finance/domain/general/human_handoff.yml +30 -0
- rasa/cli/project_templates/finance/domain/general/utils.yml +13 -0
- rasa/cli/project_templates/finance/domain/general/welcome.yml +8 -0
- rasa/cli/project_templates/finance/endpoints.yml +1 -0
- rasa/cli/project_templates/finance/prompts/rephraser_demo_personality_prompt.jinja2 +3 -3
- rasa/cli/project_templates/telco/actions/actions_billing.py +24 -17
- rasa/cli/project_templates/telco/actions/actions_get_data_from_db.py +6 -1
- rasa/cli/project_templates/telco/actions/actions_run_diagnostics.py +6 -1
- rasa/cli/project_templates/telco/actions/actions_session_start.py +6 -1
- rasa/cli/project_templates/tutorial/config.yml +2 -1
- rasa/cli/scaffold.py +27 -2
- rasa/cli/train.py +8 -0
- rasa/cli/utils.py +31 -15
- rasa/core/actions/action.py +28 -41
- rasa/core/actions/action_run_slot_rejections.py +1 -1
- rasa/core/channels/development_inspector.py +47 -14
- rasa/core/channels/inspector/dist/assets/{arc-371401b1.js → arc-1ddec37b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-3f126156.js → blockDiagram-38ab4fdb-18af387c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-12f22eb7.js → c4Diagram-3d4e48cf-250127a3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-59f6d54b.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-03b1d386.js → classDiagram-70f12bd4-c3388b34.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-84f69d63.js → classDiagram-v2-f2320105-9c893a82.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-26177ddb.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-ca47fd38.js → createText-2e5e7dd3-c111213b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-f837ca8a.js → edges-e0da2a9e-812a729d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-8717ac54.js → erDiagram-9861fffd-fd5051bc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-94f38b83.js → flowDb-956e92f1-3287ac02.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-b616f9fb.js → flowDiagram-66a62f08-692fb0b2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-29c03f5a.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-f5d24bb8.js → flowchart-elk-definition-4a651766-008376f1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-b43ba8d9.js → ganttDiagram-c361ad54-df330a69.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-c3aafaa5.js → gitGraphDiagram-72cf32ee-e03676fb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-0d0a2c10.js → graph-46fad2ba.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-58ea0305.js → index-3862675e-a484ac55.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-cce6f8a1.js → index-a003633f.js} +179 -179
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b8f60461.js → infoDiagram-f8f76790-3f9e6ec2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-95be5545.js → journeyDiagram-49397b02-79f72383.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-da885b9b.js → layout-aad098e5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-f1c817d3.js → line-219ab7ae.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-d42801e6.js → linear-2cddbe62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-a38923a6.js → mindmap-definition-fc14e90a-1d41ed99.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-ca6e71e9.js → pieDiagram-8a3498a8-cc496ee8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b290dae9.js → quadrantDiagram-120e2f19-84d32884.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-03f02ceb.js → requirementDiagram-deff3bca-c0deb984.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c49eee40.js → sankeyDiagram-04a897e0-b9d7fd62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-b2cd6a3d.js → sequenceDiagram-704730f1-7d517565.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-e53a2028.js → stateDiagram-587899a1-98ef9b27.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e1982a03.js → stateDiagram-v2-d93cdb3a-cee70748.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-d0226ca5.js → styles-6aaf32cf-3f9d1c96.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-0e21dc00.js → styles-9a916d00-67471923.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-9588494e.js → styles-c10674c1-bd093fb7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-be478d4f.js → svgDrawCommon-08f97a94-675794e8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-74631749.js → timeline-definition-85554ec2-0ac67617.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-a043552f.js → xychartDiagram-e933f94c-c018dc37.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +2 -2
- rasa/core/channels/inspector/index.html +1 -1
- rasa/core/channels/inspector/package.json +4 -3
- rasa/core/channels/inspector/src/App.tsx +53 -7
- rasa/core/channels/inspector/src/components/Chat.tsx +3 -2
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +1 -1
- rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +268 -0
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -2
- rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +8 -3
- rasa/core/channels/inspector/src/types.ts +8 -0
- rasa/core/channels/inspector/yarn.lock +12 -12
- rasa/core/channels/studio_chat.py +119 -34
- rasa/core/channels/voice_ready/twilio_voice.py +1 -1
- rasa/core/channels/voice_stream/asr/asr_engine.py +5 -1
- rasa/core/channels/voice_stream/asr/deepgram.py +5 -0
- rasa/core/channels/voice_stream/audiocodes.py +16 -8
- rasa/core/channels/voice_stream/browser_audio.py +39 -4
- rasa/core/channels/voice_stream/call_state.py +13 -2
- rasa/core/channels/voice_stream/genesys.py +16 -13
- rasa/core/channels/voice_stream/jambonz.py +14 -12
- rasa/core/channels/voice_stream/twilio_media_streams.py +14 -13
- rasa/core/channels/voice_stream/util.py +11 -1
- rasa/core/channels/voice_stream/voice_channel.py +108 -29
- rasa/core/nlg/callback.py +1 -1
- rasa/core/nlg/contextual_response_rephraser.py +19 -9
- rasa/core/nlg/generator.py +21 -5
- rasa/core/nlg/response.py +43 -6
- rasa/core/nlg/translate.py +8 -0
- rasa/core/policies/enterprise_search_policy.py +16 -21
- rasa/dialogue_understanding/commands/correct_slots_command.py +38 -10
- rasa/dialogue_understanding/generator/command_generator.py +5 -5
- rasa/dialogue_understanding/generator/command_parser.py +9 -13
- rasa/dialogue_understanding/processor/command_processor.py +149 -55
- rasa/dialogue_understanding/stack/utils.py +13 -3
- rasa/dialogue_understanding_test/du_test_schema.yml +3 -3
- rasa/dialogue_understanding_test/validation.py +9 -10
- rasa/e2e_test/e2e_config.py +18 -11
- rasa/e2e_test/e2e_test_schema.yml +3 -3
- rasa/e2e_test/utils/validation.py +17 -19
- rasa/engine/validation.py +86 -91
- rasa/exceptions.py +26 -1
- rasa/model_manager/model_api.py +2 -2
- rasa/model_manager/socket_bridge.py +8 -2
- rasa/shared/providers/_configs/default_litellm_client_config.py +3 -7
- rasa/shared/utils/cli.py +2 -0
- rasa/shared/utils/common.py +2 -1
- rasa/shared/utils/health_check/health_check.py +10 -14
- rasa/studio/upload.py +6 -2
- rasa/studio/utils.py +33 -22
- rasa/telemetry.py +95 -22
- rasa/utils/licensing.py +21 -10
- rasa/utils/log_utils.py +1 -1
- rasa/utils/tensorflow/transformer.py +3 -3
- rasa/validator.py +7 -5
- rasa/version.py +1 -1
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/METADATA +7 -7
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/RECORD +242 -203
- rasa/builder/create_openai_vector_store.py +0 -228
- rasa/builder/llm-helper-schema.json +0 -69
- rasa/builder/llm_context.py +0 -81
- rasa/builder/llm_helper_prompt.jinja2 +0 -245
- rasa/cli/project_templates/finance/data/nlu.yml +0 -29
- rasa/cli/project_templates/finance/data/patterns/pattern_search.yml +0 -5
- rasa/cli/project_templates/finance/domain/default_flows.yml +0 -33
- rasa/cli/project_templates/finance/prompts/command-generator.jinja2 +0 -57
- rasa/cli/project_templates/finance/tests/conversation_repair/cancellations.yml +0 -12
- rasa/cli/project_templates/finance/tests/conversation_repair/cannot_handle.yml +0 -7
- rasa/cli/project_templates/finance/tests/conversation_repair/chitchat.yml +0 -7
- rasa/cli/project_templates/finance/tests/conversation_repair/clarification.yml +0 -9
- rasa/cli/project_templates/finance/tests/conversation_repair/completion.yml +0 -18
- rasa/cli/project_templates/finance/tests/conversation_repair/corrections.yml +0 -17
- rasa/cli/project_templates/finance/tests/conversation_repair/digressions.yml +0 -32
- rasa/cli/project_templates/finance/tests/conversation_repair/human_handoff.yml +0 -21
- rasa/cli/project_templates/finance/tests/conversation_repair/skipping_collect_steps.yml +0 -16
- rasa/cli/project_templates/finance/tests/demo_scripts/main.yml +0 -16
- rasa/cli/project_templates/finance/tests/happy_paths/balance_verification.yml +0 -15
- rasa/cli/project_templates/finance/tests/happy_paths/banking_questions.yml +0 -12
- rasa/cli/project_templates/finance/tests/happy_paths/card_blocking.yml +0 -52
- rasa/cli/project_templates/finance/tests/happy_paths/money_transfer.yml +0 -136
- rasa/cli/project_templates/finance/tests/happy_paths/payee_management.yml +0 -27
- rasa/cli/project_templates/finance/tests/happy_paths/user_greeted.yml +0 -5
- rasa/cli/project_templates/plain/config.yml +0 -17
- rasa/cli/project_templates/plain/data/patterns/pattern_session_start.yml +0 -7
- rasa/cli/project_templates/plain/domain.yml +0 -5
- rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +0 -1
- rasa/shared/importers/static.py +0 -63
- /rasa/{cli/project_templates/plain/actions → builder/copilot}/__init__.py +0 -0
- /rasa/builder/{inkeep-rag-response-schema.json → document_retrieval/inkeep-rag-response-schema.json} +0 -0
- /rasa/cli/project_templates/finance/actions/{action_process_immediate_payment.py → transfers/action_process_immediate_payment.py} +0 -0
- /rasa/cli/project_templates/finance/actions/{action_schedule_payment.py → transfers/action_schedule_payment.py} +0 -0
- /rasa/cli/project_templates/finance/actions/{action_validate_payment_date.py → transfers/action_validate_payment_date.py} +0 -0
- /rasa/cli/project_templates/finance/data/{flows → cards}/block_card.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → cards}/select_card.yml +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/accounts.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/advisors.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/appointments.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/branches.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/cards.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/payees.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/transactions.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/users.json +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/add_payee.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/list_payees.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/remove_payee.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/transfer_money.yml +0 -0
- /rasa/cli/project_templates/finance/domain/{block_card.yml → cards/block_card.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{select_card.yml → cards/select_card.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{add_payee.yml → transfers/add_payee.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{list_payees.yml → transfers/list_payees.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{remove_payee.yml → transfers/remove_payee.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{transfer_money.yml → transfers/transfer_money.yml} +0 -0
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/entry_points.txt +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import audioop
|
|
3
5
|
import base64
|
|
4
6
|
import json
|
|
7
|
+
import time
|
|
5
8
|
import uuid
|
|
6
9
|
from functools import partial
|
|
7
10
|
from typing import (
|
|
@@ -14,6 +17,7 @@ from typing import (
|
|
|
14
17
|
Optional,
|
|
15
18
|
Text,
|
|
16
19
|
Tuple,
|
|
20
|
+
Union,
|
|
17
21
|
)
|
|
18
22
|
|
|
19
23
|
import structlog
|
|
@@ -32,6 +36,7 @@ from rasa.core.channels.voice_stream.voice_channel import (
|
|
|
32
36
|
VoiceInputChannel,
|
|
33
37
|
VoiceOutputChannel,
|
|
34
38
|
)
|
|
39
|
+
from rasa.core.exceptions import AgentNotReady
|
|
35
40
|
from rasa.hooks import hookimpl
|
|
36
41
|
from rasa.plugin import plugin_manager
|
|
37
42
|
from rasa.shared.core.constants import ACTION_LISTEN_NAME
|
|
@@ -49,7 +54,9 @@ if TYPE_CHECKING:
|
|
|
49
54
|
structlogger = structlog.get_logger()
|
|
50
55
|
|
|
51
56
|
|
|
52
|
-
def tracker_as_dump(
|
|
57
|
+
def tracker_as_dump(
|
|
58
|
+
tracker: "DialogueStateTracker", latency: Optional[float] = None
|
|
59
|
+
) -> Dict[str, Any]:
|
|
53
60
|
"""Create a dump of the tracker state."""
|
|
54
61
|
from rasa.shared.core.trackers import get_trackers_for_conversation_sessions
|
|
55
62
|
|
|
@@ -62,7 +69,12 @@ def tracker_as_dump(tracker: "DialogueStateTracker") -> Dict[str, Any]:
|
|
|
62
69
|
|
|
63
70
|
# TODO: this is a bug: the bridge converts this back to json, but it
|
|
64
71
|
# should be json in the first place
|
|
65
|
-
|
|
72
|
+
state = last_tracker.current_state(EventVerbosity.AFTER_RESTART)
|
|
73
|
+
|
|
74
|
+
if latency is not None:
|
|
75
|
+
state["latency"] = {"rasa_processing_latency_ms": latency}
|
|
76
|
+
|
|
77
|
+
return state
|
|
66
78
|
|
|
67
79
|
|
|
68
80
|
def does_need_action_prediction(tracker: "DialogueStateTracker") -> bool:
|
|
@@ -149,7 +161,6 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
149
161
|
from rasa.core.agent import Agent
|
|
150
162
|
|
|
151
163
|
self.agent: Optional[Agent] = None
|
|
152
|
-
self.latest_tracker_session_id = None
|
|
153
164
|
|
|
154
165
|
# Initialize the SocketIO input channel
|
|
155
166
|
SocketIOInput.__init__(
|
|
@@ -177,6 +188,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
177
188
|
# `background_tasks` holds the asyncio tasks for voice streaming
|
|
178
189
|
self.active_connections: Dict[str, SocketIOVoiceWebsocketAdapter] = {}
|
|
179
190
|
self.background_tasks: Dict[str, asyncio.Task] = {}
|
|
191
|
+
self._turn_start_times: Dict[Text, float] = {}
|
|
180
192
|
|
|
181
193
|
self._register_tracker_update_hook()
|
|
182
194
|
|
|
@@ -203,24 +215,41 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
203
215
|
metadata_key=credentials.get("metadata_key", "metadata"),
|
|
204
216
|
)
|
|
205
217
|
|
|
218
|
+
async def emit(self, event: str, data: Union[Dict, str], room: str) -> None:
|
|
219
|
+
"""Emits an event to the websocket."""
|
|
220
|
+
if not self.sio:
|
|
221
|
+
structlogger.error("studio_chat.emit.sio_not_initialized")
|
|
222
|
+
return
|
|
223
|
+
await self.sio.emit(event, data, room=room)
|
|
224
|
+
|
|
206
225
|
def _register_tracker_update_hook(self) -> None:
|
|
207
226
|
plugin_manager().register(StudioTrackerUpdatePlugin(self))
|
|
208
227
|
|
|
209
|
-
async def on_tracker_updated(
|
|
228
|
+
async def on_tracker_updated(
|
|
229
|
+
self, tracker: "DialogueStateTracker", latency: Optional[float] = None
|
|
230
|
+
) -> None:
|
|
210
231
|
"""Triggers a tracker update notification after a change to the tracker."""
|
|
211
|
-
await self.publish_tracker_update(
|
|
232
|
+
await self.publish_tracker_update(
|
|
233
|
+
tracker.sender_id, tracker_as_dump(tracker, latency)
|
|
234
|
+
)
|
|
212
235
|
|
|
213
|
-
async def publish_tracker_update(self, sender_id: str, tracker_dump:
|
|
236
|
+
async def publish_tracker_update(self, sender_id: str, tracker_dump: str) -> None:
|
|
214
237
|
"""Publishes a tracker update notification to the websocket."""
|
|
215
|
-
|
|
216
|
-
structlogger.error("studio_chat.on_tracker_updated.sio_not_initialized")
|
|
217
|
-
return
|
|
238
|
+
await self.emit("tracker", tracker_dump, room=sender_id)
|
|
218
239
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
self.
|
|
240
|
+
def _record_turn_start_time(self, sender_id: Text) -> None:
|
|
241
|
+
"""Records the start time of a new turn."""
|
|
242
|
+
self._turn_start_times[sender_id] = time.time()
|
|
243
|
+
|
|
244
|
+
def _get_latency(self, sender_id: Text) -> Optional[float]:
|
|
245
|
+
"""Returns the latency of the current turn in milliseconds."""
|
|
246
|
+
if sender_id not in self._turn_start_times:
|
|
247
|
+
return None
|
|
222
248
|
|
|
223
|
-
|
|
249
|
+
latency = (time.time() - self._turn_start_times[sender_id]) * 1000
|
|
250
|
+
# The turn is over, so we can remove the start time
|
|
251
|
+
del self._turn_start_times[sender_id]
|
|
252
|
+
return latency
|
|
224
253
|
|
|
225
254
|
async def on_message_proxy(
|
|
226
255
|
self,
|
|
@@ -231,6 +260,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
231
260
|
|
|
232
261
|
Triggers a tracker update notification after processing the message.
|
|
233
262
|
"""
|
|
263
|
+
self._record_turn_start_time(message.sender_id)
|
|
234
264
|
try:
|
|
235
265
|
await on_new_message(message)
|
|
236
266
|
except Exception as e:
|
|
@@ -240,8 +270,15 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
240
270
|
sender_id=message.sender_id,
|
|
241
271
|
)
|
|
242
272
|
|
|
243
|
-
if not self.agent:
|
|
273
|
+
if not self.agent or not self.agent.is_ready():
|
|
244
274
|
structlogger.error("studio_chat.on_message_proxy.agent_not_initialized")
|
|
275
|
+
await self.emit_error(
|
|
276
|
+
"The Rasa Pro model could not be loaded. "
|
|
277
|
+
"Please check the training and deployment logs "
|
|
278
|
+
"for more information.",
|
|
279
|
+
message.sender_id,
|
|
280
|
+
AgentNotReady("The Rasa Pro model could not be loaded."),
|
|
281
|
+
)
|
|
245
282
|
return
|
|
246
283
|
|
|
247
284
|
tracker = await self.agent.tracker_store.retrieve(message.sender_id)
|
|
@@ -249,7 +286,19 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
249
286
|
structlogger.error("studio_chat.on_message_proxy.tracker_not_found")
|
|
250
287
|
return
|
|
251
288
|
|
|
252
|
-
|
|
289
|
+
latency = self._get_latency(message.sender_id)
|
|
290
|
+
await self.on_tracker_updated(tracker, latency)
|
|
291
|
+
|
|
292
|
+
async def emit_error(self, message: str, room: str, e: Exception) -> None:
|
|
293
|
+
await self.emit(
|
|
294
|
+
"error",
|
|
295
|
+
{
|
|
296
|
+
"message": message,
|
|
297
|
+
"error": str(e),
|
|
298
|
+
"exception": str(type(e).__name__),
|
|
299
|
+
},
|
|
300
|
+
room=room,
|
|
301
|
+
)
|
|
253
302
|
|
|
254
303
|
async def handle_tracker_update(self, sid: str, data: Dict) -> None:
|
|
255
304
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
@@ -267,21 +316,41 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
267
316
|
structlogger.error("studio_chat.sio.domain_not_initialized")
|
|
268
317
|
return None
|
|
269
318
|
|
|
319
|
+
tracker: Optional[DialogueStateTracker] = None
|
|
320
|
+
|
|
270
321
|
async with self.agent.lock_store.lock(data["sender_id"]):
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
322
|
+
try:
|
|
323
|
+
tracker = DialogueStateTracker.from_dict(
|
|
324
|
+
data["sender_id"], data["events"], domain.slots
|
|
325
|
+
)
|
|
274
326
|
|
|
275
|
-
|
|
276
|
-
|
|
327
|
+
# will override an existing tracker with the same id!
|
|
328
|
+
await self.agent.tracker_store.save(tracker)
|
|
277
329
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
330
|
+
processor = self.agent.processor
|
|
331
|
+
if processor and does_need_action_prediction(tracker):
|
|
332
|
+
output_channel = self.get_output_channel()
|
|
281
333
|
|
|
282
|
-
|
|
283
|
-
|
|
334
|
+
await processor._run_prediction_loop(output_channel, tracker)
|
|
335
|
+
await self.agent.tracker_store.save(tracker)
|
|
336
|
+
except Exception as e:
|
|
337
|
+
structlogger.error(
|
|
338
|
+
"studio_chat.sio.handle_tracker_update.error",
|
|
339
|
+
error=e,
|
|
340
|
+
sender_id=data["sender_id"],
|
|
341
|
+
)
|
|
342
|
+
await self.emit_error(
|
|
343
|
+
"An error occurred while updating the conversation.",
|
|
344
|
+
data["sender_id"],
|
|
345
|
+
e,
|
|
346
|
+
)
|
|
284
347
|
|
|
348
|
+
if not tracker:
|
|
349
|
+
# in case the tracker couldn't be updated, we retrieve the prior
|
|
350
|
+
# version and use that to populate the update
|
|
351
|
+
tracker = await self.agent.tracker_store.get_or_create_tracker(
|
|
352
|
+
data["sender_id"]
|
|
353
|
+
)
|
|
285
354
|
await self.on_tracker_updated(tracker)
|
|
286
355
|
|
|
287
356
|
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
@@ -308,14 +377,14 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
308
377
|
elif "marker" in message:
|
|
309
378
|
if message["marker"] == call_state.latest_bot_audio_id:
|
|
310
379
|
# Just finished streaming last audio bytes
|
|
311
|
-
call_state.is_bot_speaking = False
|
|
380
|
+
call_state.is_bot_speaking = False
|
|
312
381
|
if call_state.should_hangup:
|
|
313
382
|
structlogger.debug(
|
|
314
383
|
"studio_chat.hangup", marker=call_state.latest_bot_audio_id
|
|
315
384
|
)
|
|
316
385
|
return EndConversationAction()
|
|
317
386
|
else:
|
|
318
|
-
call_state.is_bot_speaking = True
|
|
387
|
+
call_state.is_bot_speaking = True
|
|
319
388
|
return ContinueConversationAction()
|
|
320
389
|
|
|
321
390
|
def create_output_channel(
|
|
@@ -398,9 +467,8 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
398
467
|
def blueprint(
|
|
399
468
|
self, on_new_message: Callable[["UserMessage"], Awaitable[Any]]
|
|
400
469
|
) -> SocketBlueprint:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
)
|
|
470
|
+
proxied_on_message = partial(self.on_message_proxy, on_new_message)
|
|
471
|
+
socket_blueprint = super().blueprint(proxied_on_message)
|
|
404
472
|
|
|
405
473
|
if not self.sio:
|
|
406
474
|
structlogger.error("studio_chat.blueprint.sio_not_initialized")
|
|
@@ -435,7 +503,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
435
503
|
|
|
436
504
|
# start a voice session if requested
|
|
437
505
|
if data and data.get("is_voice", False):
|
|
438
|
-
self._start_voice_session(data["session_id"], sid,
|
|
506
|
+
self._start_voice_session(data["session_id"], sid, proxied_on_message)
|
|
439
507
|
|
|
440
508
|
@self.sio.on(self.user_message_evt, namespace=self.namespace)
|
|
441
509
|
async def handle_message(sid: Text, data: Dict) -> None:
|
|
@@ -450,14 +518,14 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
|
|
|
450
518
|
|
|
451
519
|
try:
|
|
452
520
|
# Handle text messages
|
|
453
|
-
await self.handle_user_message(sid, data,
|
|
521
|
+
await self.handle_user_message(sid, data, proxied_on_message)
|
|
454
522
|
except Exception as e:
|
|
455
523
|
structlogger.exception(
|
|
456
524
|
"studio_chat.sio.handle_message.error",
|
|
457
525
|
error=str(e),
|
|
458
526
|
sid=sid,
|
|
459
527
|
)
|
|
460
|
-
await self.
|
|
528
|
+
await self.emit("error", str(e), room=sid)
|
|
461
529
|
|
|
462
530
|
@self.sio.on("update_tracker", namespace=self.namespace)
|
|
463
531
|
async def on_update_tracker(sid: Text, data: Dict) -> None:
|
|
@@ -481,7 +549,24 @@ class StudioVoiceOutputChannel(VoiceOutputChannel):
|
|
|
481
549
|
|
|
482
550
|
def create_marker_message(self, recipient_id: str) -> Tuple[str, str]:
|
|
483
551
|
message_id = uuid.uuid4().hex
|
|
484
|
-
|
|
552
|
+
marker_data = {"marker": message_id}
|
|
553
|
+
|
|
554
|
+
# Include comprehensive latency information if available
|
|
555
|
+
latency_data = {
|
|
556
|
+
"asr_latency_ms": call_state.asr_latency_ms,
|
|
557
|
+
"rasa_processing_latency_ms": call_state.rasa_processing_latency_ms,
|
|
558
|
+
"tts_first_byte_latency_ms": call_state.tts_first_byte_latency_ms,
|
|
559
|
+
"tts_complete_latency_ms": call_state.tts_complete_latency_ms,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
# Filter out None values from latency data
|
|
563
|
+
latency_data = {k: v for k, v in latency_data.items() if v is not None}
|
|
564
|
+
|
|
565
|
+
# Add latency data to marker if any metrics are available
|
|
566
|
+
if latency_data:
|
|
567
|
+
marker_data["latency"] = latency_data # type: ignore[assignment]
|
|
568
|
+
|
|
569
|
+
return json.dumps(marker_data), message_id
|
|
485
570
|
|
|
486
571
|
|
|
487
572
|
class SocketIOVoiceWebsocketAdapter:
|
|
@@ -30,7 +30,7 @@ TWILIO_VOICE_PATH = "webhooks/twilio_voice/webhook"
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def map_call_params(form: RequestParameters) -> CallParameters:
|
|
33
|
-
"""Map the
|
|
33
|
+
"""Map the Twilio Voice parameters to the CallParameters dataclass."""
|
|
34
34
|
return CallParameters(
|
|
35
35
|
call_id=form.get("CallSid"),
|
|
36
36
|
user_phone=form.get("Caller"),
|
|
@@ -26,7 +26,7 @@ logger = structlog.get_logger(__name__)
|
|
|
26
26
|
|
|
27
27
|
@dataclass
|
|
28
28
|
class ASREngineConfig(MergeableConfig):
|
|
29
|
-
|
|
29
|
+
keep_alive_interval: int = 5 # seconds
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class ASREngine(Generic[T]):
|
|
@@ -93,3 +93,7 @@ class ASREngine(Generic[T]):
|
|
|
93
93
|
def get_default_config() -> T:
|
|
94
94
|
"""Get the default config for this component."""
|
|
95
95
|
raise NotImplementedError
|
|
96
|
+
|
|
97
|
+
async def send_keep_alive(self) -> None:
|
|
98
|
+
"""Send a keep-alive message to the ASR system if supported."""
|
|
99
|
+
pass
|
|
@@ -145,3 +145,8 @@ class DeepgramASR(ASREngine[DeepgramASRConfig]):
|
|
|
145
145
|
def concatenate_transcripts(t1: str, t2: str) -> str:
|
|
146
146
|
"""Concatenate two transcripts making sure there is a space between them."""
|
|
147
147
|
return (t1.strip() + " " + t2.strip()).strip()
|
|
148
|
+
|
|
149
|
+
async def send_keep_alive(self) -> None:
|
|
150
|
+
"""Send a keep-alive message to the Deepgram websocket connection."""
|
|
151
|
+
if self.asr_socket is not None:
|
|
152
|
+
await self.asr_socket.send(json.dumps({"type": "KeepAlive"}))
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import base64
|
|
3
5
|
import hmac
|
|
@@ -21,6 +23,7 @@ from rasa.core.channels.voice_stream.call_state import (
|
|
|
21
23
|
call_state,
|
|
22
24
|
)
|
|
23
25
|
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
26
|
+
from rasa.core.channels.voice_stream.util import repack_voice_credentials
|
|
24
27
|
from rasa.core.channels.voice_stream.voice_channel import (
|
|
25
28
|
ContinueConversationAction,
|
|
26
29
|
EndConversationAction,
|
|
@@ -81,6 +84,12 @@ class AudiocodesVoiceOutputChannel(VoiceOutputChannel):
|
|
|
81
84
|
logger.debug("Sending start marker", stream_id=self._get_stream_id())
|
|
82
85
|
await self.voice_websocket.send(media_message)
|
|
83
86
|
|
|
87
|
+
# This should be set when the bot actually starts speaking
|
|
88
|
+
# however, Audiocodes does not have an event to indicate that.
|
|
89
|
+
# This is an approximation, as the bot will be sent the audio chunks next
|
|
90
|
+
# which are played to the user immediately.
|
|
91
|
+
call_state.is_bot_speaking = True
|
|
92
|
+
|
|
84
93
|
async def send_intermediate_marker(self, recipient_id: str) -> None:
|
|
85
94
|
"""Audiocodes doesn't need intermediate markers, so do nothing."""
|
|
86
95
|
pass
|
|
@@ -121,10 +130,10 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
121
130
|
def from_credentials(
|
|
122
131
|
cls,
|
|
123
132
|
credentials: Optional[Dict[str, Any]],
|
|
124
|
-
) ->
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
133
|
+
) -> AudiocodesVoiceInputChannel:
|
|
134
|
+
cls.validate_basic_credentials(credentials)
|
|
135
|
+
new_creds = repack_voice_credentials(credentials)
|
|
136
|
+
return cls(**new_creds)
|
|
128
137
|
|
|
129
138
|
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
130
139
|
return RasaAudioBytes(base64.b64decode(input_bytes))
|
|
@@ -170,21 +179,20 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
170
179
|
if data["type"] == "activities":
|
|
171
180
|
activities = data["activities"]
|
|
172
181
|
for activity in activities:
|
|
173
|
-
logger.debug("audiocodes_stream.activity", data=activity)
|
|
174
182
|
if activity["name"] == "start":
|
|
175
|
-
#
|
|
183
|
+
# handled in collect_call_parameters
|
|
176
184
|
pass
|
|
177
185
|
elif activity["name"] == "dtmf":
|
|
178
|
-
|
|
186
|
+
logger.info("audiocodes_stream.dtmf_ignored", data=activity)
|
|
179
187
|
pass
|
|
180
188
|
elif activity["name"] == "playFinished":
|
|
181
189
|
logger.debug("audiocodes_stream.playFinished", data=activity)
|
|
190
|
+
call_state.is_bot_speaking = False
|
|
182
191
|
if call_state.should_hangup:
|
|
183
192
|
logger.info("audiocodes_stream.hangup")
|
|
184
193
|
self._send_hangup(ws, data)
|
|
185
194
|
# the conversation should continue until
|
|
186
195
|
# we receive a end message from audiocodes
|
|
187
|
-
pass
|
|
188
196
|
else:
|
|
189
197
|
logger.warning("audiocodes_stream.unknown_activity", data=activity)
|
|
190
198
|
elif data["type"] == "userStream.start":
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import audioop
|
|
2
4
|
import base64
|
|
3
5
|
import json
|
|
4
6
|
import uuid
|
|
5
|
-
from typing import Any, Awaitable, Callable, Optional, Tuple
|
|
7
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple
|
|
6
8
|
|
|
7
9
|
import structlog
|
|
8
10
|
from sanic import ( # type: ignore[attr-defined]
|
|
@@ -18,6 +20,7 @@ from rasa.core.channels.voice_ready.utils import CallParameters
|
|
|
18
20
|
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
19
21
|
from rasa.core.channels.voice_stream.call_state import call_state
|
|
20
22
|
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
23
|
+
from rasa.core.channels.voice_stream.util import repack_voice_credentials
|
|
21
24
|
from rasa.core.channels.voice_stream.voice_channel import (
|
|
22
25
|
ContinueConversationAction,
|
|
23
26
|
EndConversationAction,
|
|
@@ -45,10 +48,33 @@ class BrowserAudioOutputChannel(VoiceOutputChannel):
|
|
|
45
48
|
|
|
46
49
|
def create_marker_message(self, recipient_id: str) -> Tuple[str, str]:
|
|
47
50
|
message_id = uuid.uuid4().hex
|
|
48
|
-
|
|
51
|
+
marker_data = {"marker": message_id}
|
|
52
|
+
|
|
53
|
+
# Include comprehensive latency information if available
|
|
54
|
+
latency_data = {
|
|
55
|
+
"asr_latency_ms": call_state.asr_latency_ms,
|
|
56
|
+
"rasa_processing_latency_ms": call_state.rasa_processing_latency_ms,
|
|
57
|
+
"tts_first_byte_latency_ms": call_state.tts_first_byte_latency_ms,
|
|
58
|
+
"tts_complete_latency_ms": call_state.tts_complete_latency_ms,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Filter out None values from latency data
|
|
62
|
+
latency_data = {k: v for k, v in latency_data.items() if v is not None}
|
|
63
|
+
|
|
64
|
+
# Add latency data to marker if any metrics are available
|
|
65
|
+
if latency_data:
|
|
66
|
+
marker_data["latency"] = latency_data # type: ignore[assignment]
|
|
67
|
+
|
|
68
|
+
return json.dumps(marker_data), message_id
|
|
49
69
|
|
|
50
70
|
|
|
51
71
|
class BrowserAudioInputChannel(VoiceInputChannel):
|
|
72
|
+
def __init__(
|
|
73
|
+
self, server_url: str, asr_config: Dict[str, Any], tts_config: Dict[str, Any]
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Initializes the browser audio input channel."""
|
|
76
|
+
super().__init__(server_url, asr_config, tts_config)
|
|
77
|
+
|
|
52
78
|
@classmethod
|
|
53
79
|
def name(cls) -> str:
|
|
54
80
|
return "browser_audio"
|
|
@@ -62,6 +88,15 @@ class BrowserAudioInputChannel(VoiceInputChannel):
|
|
|
62
88
|
call_id = f"inspect-{uuid.uuid4()}"
|
|
63
89
|
return CallParameters(call_id, "local", "local", stream_id=call_id)
|
|
64
90
|
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_credentials(
|
|
93
|
+
cls,
|
|
94
|
+
credentials: Optional[Dict[str, Any]],
|
|
95
|
+
) -> BrowserAudioInputChannel:
|
|
96
|
+
cls.validate_basic_credentials(credentials)
|
|
97
|
+
new_creds = repack_voice_credentials(credentials)
|
|
98
|
+
return cls(**new_creds)
|
|
99
|
+
|
|
65
100
|
def map_input_message(
|
|
66
101
|
self,
|
|
67
102
|
message: Any,
|
|
@@ -75,14 +110,14 @@ class BrowserAudioInputChannel(VoiceInputChannel):
|
|
|
75
110
|
elif "marker" in data:
|
|
76
111
|
if data["marker"] == call_state.latest_bot_audio_id:
|
|
77
112
|
# Just finished streaming last audio bytes
|
|
78
|
-
call_state.is_bot_speaking = False
|
|
113
|
+
call_state.is_bot_speaking = False
|
|
79
114
|
if call_state.should_hangup:
|
|
80
115
|
logger.debug(
|
|
81
116
|
"browser_audio.hangup", marker=call_state.latest_bot_audio_id
|
|
82
117
|
)
|
|
83
118
|
return EndConversationAction()
|
|
84
119
|
else:
|
|
85
|
-
call_state.is_bot_speaking = True
|
|
120
|
+
call_state.is_bot_speaking = True
|
|
86
121
|
return ContinueConversationAction()
|
|
87
122
|
|
|
88
123
|
def create_output_channel(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from contextvars import ContextVar
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any, Dict, Optional
|
|
4
|
+
from typing import Any, Dict, Optional, cast
|
|
5
5
|
|
|
6
6
|
from werkzeug.local import LocalProxy
|
|
7
7
|
|
|
@@ -19,9 +19,20 @@ class CallState:
|
|
|
19
19
|
should_hangup: bool = False
|
|
20
20
|
connection_failed: bool = False
|
|
21
21
|
|
|
22
|
+
# Latency tracking - start times only
|
|
23
|
+
user_speech_start_time: Optional[float] = None
|
|
24
|
+
rasa_processing_start_time: Optional[float] = None
|
|
25
|
+
tts_start_time: Optional[float] = None
|
|
26
|
+
|
|
27
|
+
# Calculated latencies (used by channels like browser_audio)
|
|
28
|
+
asr_latency_ms: Optional[float] = None
|
|
29
|
+
rasa_processing_latency_ms: Optional[float] = None
|
|
30
|
+
tts_first_byte_latency_ms: Optional[float] = None
|
|
31
|
+
tts_complete_latency_ms: Optional[float] = None
|
|
32
|
+
|
|
22
33
|
# Generic field for channel-specific state data
|
|
23
34
|
channel_data: Dict[str, Any] = field(default_factory=dict)
|
|
24
35
|
|
|
25
36
|
|
|
26
37
|
_call_state: ContextVar[CallState] = ContextVar("call_state")
|
|
27
|
-
call_state = LocalProxy(_call_state)
|
|
38
|
+
call_state: CallState = cast(CallState, LocalProxy(_call_state))
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import base64
|
|
3
5
|
import hashlib
|
|
@@ -21,6 +23,7 @@ from rasa.core.channels.voice_stream.call_state import (
|
|
|
21
23
|
call_state,
|
|
22
24
|
)
|
|
23
25
|
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
26
|
+
from rasa.core.channels.voice_stream.util import repack_voice_credentials
|
|
24
27
|
from rasa.core.channels.voice_stream.voice_channel import (
|
|
25
28
|
ContinueConversationAction,
|
|
26
29
|
EndConversationAction,
|
|
@@ -54,7 +57,7 @@ logger = structlog.get_logger(__name__)
|
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
def map_call_params(data: Dict[Text, Any]) -> CallParameters:
|
|
57
|
-
"""Map the
|
|
60
|
+
"""Map the Genesys parameters to the CallParameters dataclass."""
|
|
58
61
|
parameters = data["parameters"]
|
|
59
62
|
participant = parameters["participant"]
|
|
60
63
|
# sent as {"ani": "tel:+491604697810"}
|
|
@@ -107,7 +110,7 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
107
110
|
def from_credentials(
|
|
108
111
|
cls,
|
|
109
112
|
credentials: Optional[Dict[str, Any]],
|
|
110
|
-
) ->
|
|
113
|
+
) -> GenesysInputChannel:
|
|
111
114
|
"""Create a channel from credentials dictionary.
|
|
112
115
|
|
|
113
116
|
Args:
|
|
@@ -121,21 +124,21 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
121
124
|
Returns:
|
|
122
125
|
GenesysInputChannel instance
|
|
123
126
|
"""
|
|
124
|
-
|
|
127
|
+
cls.validate_credentials(credentials)
|
|
128
|
+
new_creds = repack_voice_credentials(credentials)
|
|
129
|
+
return cls(**new_creds)
|
|
125
130
|
|
|
126
|
-
|
|
131
|
+
@classmethod
|
|
132
|
+
def validate_credentials(cls, credentials: Optional[Dict[str, Any]]) -> None:
|
|
133
|
+
"""Validate the credentials for the Genesys voice channel."""
|
|
134
|
+
cls.validate_basic_credentials(credentials)
|
|
127
135
|
if not credentials.get("api_key"): # type: ignore[union-attr]
|
|
128
136
|
raise InvalidConfigException(
|
|
129
137
|
"No API key given for Genesys voice channel (api_key)."
|
|
130
138
|
)
|
|
131
139
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
channel.client_secret = credentials.get("client_secret") # type: ignore[union-attr,attr-defined]
|
|
135
|
-
|
|
136
|
-
return channel # type: ignore[return-value]
|
|
137
|
-
|
|
138
|
-
def _ensure_channel_data_initialized(self) -> None:
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _ensure_channel_data_initialized() -> None:
|
|
139
142
|
"""Initialize Genesys-specific channel data if not already present.
|
|
140
143
|
|
|
141
144
|
Genesys requires the server and client each maintain a
|
|
@@ -216,10 +219,10 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
216
219
|
self.handle_ping(ws, data)
|
|
217
220
|
elif msg_type == "playback_started":
|
|
218
221
|
logger.debug("genesys.handle_playback_started", message=data)
|
|
219
|
-
call_state.is_bot_speaking = True
|
|
222
|
+
call_state.is_bot_speaking = True
|
|
220
223
|
elif msg_type == "playback_completed":
|
|
221
224
|
logger.debug("genesys.handle_playback_completed", message=data)
|
|
222
|
-
call_state.is_bot_speaking = False
|
|
225
|
+
call_state.is_bot_speaking = False
|
|
223
226
|
if call_state.should_hangup:
|
|
224
227
|
logger.info("genesys.hangup")
|
|
225
228
|
self.disconnect(ws, data)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import audioop
|
|
2
4
|
import json
|
|
3
5
|
import uuid
|
|
@@ -20,6 +22,7 @@ from rasa.core.channels.voice_ready.utils import (
|
|
|
20
22
|
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
21
23
|
from rasa.core.channels.voice_stream.call_state import call_state
|
|
22
24
|
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
25
|
+
from rasa.core.channels.voice_stream.util import repack_voice_credentials
|
|
23
26
|
from rasa.core.channels.voice_stream.voice_channel import (
|
|
24
27
|
ContinueConversationAction,
|
|
25
28
|
EndConversationAction,
|
|
@@ -31,11 +34,11 @@ from rasa.core.channels.voice_stream.voice_channel import (
|
|
|
31
34
|
|
|
32
35
|
logger = structlog.get_logger()
|
|
33
36
|
|
|
34
|
-
JAMBONZ_STREAMS_WEBSOCKET_PATH = "webhooks/
|
|
37
|
+
JAMBONZ_STREAMS_WEBSOCKET_PATH = "webhooks/jambonz_stream/websocket"
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def map_call_params(data: Dict[Text, str]) -> CallParameters:
|
|
38
|
-
"""Map the
|
|
41
|
+
"""Map the Jambonz stream parameters to the CallParameters dataclass."""
|
|
39
42
|
call_sid = data.get("callSid", "None")
|
|
40
43
|
from_number = data.get("from", "Unknown")
|
|
41
44
|
to_number = data.get("to")
|
|
@@ -94,7 +97,7 @@ class JambonzStreamInputChannel(VoiceInputChannel):
|
|
|
94
97
|
@classmethod
|
|
95
98
|
def from_credentials(
|
|
96
99
|
cls, credentials: Optional[Dict[Text, Any]]
|
|
97
|
-
) ->
|
|
100
|
+
) -> JambonzStreamInputChannel:
|
|
98
101
|
"""Create a channel from credentials dictionary.
|
|
99
102
|
|
|
100
103
|
Args:
|
|
@@ -109,19 +112,18 @@ class JambonzStreamInputChannel(VoiceInputChannel):
|
|
|
109
112
|
JambonzStreamInputChannel instance
|
|
110
113
|
"""
|
|
111
114
|
# Get common credentials from parent
|
|
112
|
-
|
|
115
|
+
cls.validate_credentials(credentials)
|
|
116
|
+
new_creds = repack_voice_credentials(credentials)
|
|
117
|
+
return cls(**new_creds)
|
|
113
118
|
|
|
119
|
+
@classmethod
|
|
120
|
+
def validate_credentials(cls, credentials: Optional[Dict[Text, Any]]) -> None:
|
|
121
|
+
cls.validate_basic_credentials(credentials)
|
|
114
122
|
# Check optional basic auth credentials
|
|
115
123
|
username = credentials.get("username") # type: ignore[union-attr]
|
|
116
124
|
password = credentials.get("password") # type: ignore[union-attr]
|
|
117
125
|
validate_username_password_credentials(username, password, "Jambonz Stream")
|
|
118
126
|
|
|
119
|
-
# Update channel with auth credentials
|
|
120
|
-
channel.username = username # type: ignore[attr-defined]
|
|
121
|
-
channel.password = password # type: ignore[attr-defined]
|
|
122
|
-
|
|
123
|
-
return channel # type: ignore[return-value]
|
|
124
|
-
|
|
125
127
|
def _websocket_stream_url(self) -> str:
|
|
126
128
|
"""Returns the websocket stream URL."""
|
|
127
129
|
# depending on the config value, the url might contain http as a
|
|
@@ -158,14 +160,14 @@ class JambonzStreamInputChannel(VoiceInputChannel):
|
|
|
158
160
|
if data["type"] == "mark":
|
|
159
161
|
if data["data"]["name"] == call_state.latest_bot_audio_id:
|
|
160
162
|
# Just finished streaming last audio bytes
|
|
161
|
-
call_state.is_bot_speaking = False
|
|
163
|
+
call_state.is_bot_speaking = False
|
|
162
164
|
if call_state.should_hangup:
|
|
163
165
|
logger.debug(
|
|
164
166
|
"jambonz.hangup", marker=call_state.latest_bot_audio_id
|
|
165
167
|
)
|
|
166
168
|
return EndConversationAction()
|
|
167
169
|
else:
|
|
168
|
-
call_state.is_bot_speaking = True
|
|
170
|
+
call_state.is_bot_speaking = True
|
|
169
171
|
elif data["event"] == "dtmf":
|
|
170
172
|
# TODO: handle DTMF input
|
|
171
173
|
logger.debug("jambonz.dtmf.received", dtmf=data["dtmf"])
|