rasa-pro 3.13.1a19__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} +24 -30
- 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 +59 -7
- rasa/builder/logging_utils.py +162 -4
- rasa/builder/main.py +29 -16
- rasa/builder/models.py +90 -233
- rasa/builder/project_generator.py +91 -7
- rasa/builder/scrape_rasa_docs.py +1 -1
- rasa/builder/service.py +632 -440
- 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.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/METADATA +7 -7
- {rasa_pro-3.13.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/RECORD +242 -205
- rasa/builder/constants.py +0 -4
- rasa/builder/copilot-llm-structured-output-response-schema.json +0 -69
- rasa/builder/copilot.py +0 -233
- rasa/builder/copilot_system_prompt.jinja2 +0 -245
- rasa/builder/create_openai_vector_store.py +0 -228
- rasa/builder/llm_context.py +0 -81
- 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.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/entry_points.txt +0 -0
rasa/builder/service.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# mypy: disable-error-code=misc
|
|
2
|
+
import asyncio
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
+
import time
|
|
6
|
+
from http import HTTPStatus
|
|
5
7
|
from typing import Any, Optional
|
|
6
8
|
|
|
7
9
|
import structlog
|
|
@@ -9,30 +11,61 @@ from sanic import Blueprint, HTTPResponse, response
|
|
|
9
11
|
from sanic.request import Request
|
|
10
12
|
from sanic_openapi import openapi
|
|
11
13
|
|
|
12
|
-
from rasa.builder.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
from rasa.builder.auth import HEADER_USER_ID, extract_and_verify_auth0_token
|
|
15
|
+
from rasa.builder.config import (
|
|
16
|
+
COPILOT_ASSISTANT_TRACKER_MAX_TURNS,
|
|
17
|
+
COPILOT_HANDLER_ROLLING_BUFFER_SIZE,
|
|
18
|
+
HELLO_RASA_PROJECT_ID,
|
|
19
|
+
)
|
|
20
|
+
from rasa.builder.copilot.constants import ROLE_USER
|
|
21
|
+
from rasa.builder.copilot.exceptions import CopilotStreamError
|
|
22
|
+
from rasa.builder.copilot.models import (
|
|
23
|
+
CopilotContext,
|
|
24
|
+
CopilotRequest,
|
|
25
|
+
GeneratedContent,
|
|
26
|
+
ReferenceEntry,
|
|
27
|
+
ReferenceSection,
|
|
28
|
+
ResponseCategory,
|
|
29
|
+
ResponseCompleteness,
|
|
30
|
+
)
|
|
31
|
+
from rasa.builder.copilot.telemetry import CopilotTelemetry
|
|
32
|
+
from rasa.builder.download import create_bot_project_archive
|
|
33
|
+
from rasa.builder.guardrails.utils import (
|
|
34
|
+
check_assistant_chat_for_policy_violations,
|
|
35
|
+
check_copilot_chat_for_policy_violations,
|
|
36
|
+
)
|
|
37
|
+
from rasa.builder.job_manager import job_manager
|
|
38
|
+
from rasa.builder.jobs import (
|
|
39
|
+
run_prompt_to_bot_job,
|
|
40
|
+
run_template_to_bot_job,
|
|
41
|
+
run_update_files_job,
|
|
17
42
|
)
|
|
18
43
|
from rasa.builder.llm_service import llm_service
|
|
19
|
-
from rasa.builder.logging_utils import
|
|
44
|
+
from rasa.builder.logging_utils import (
|
|
45
|
+
capture_exception_with_context,
|
|
46
|
+
get_recent_logs,
|
|
47
|
+
)
|
|
20
48
|
from rasa.builder.models import (
|
|
21
49
|
ApiErrorResponse,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
50
|
+
AssistantInfo,
|
|
51
|
+
BotData,
|
|
52
|
+
JobCreateResponse,
|
|
53
|
+
JobStatus,
|
|
54
|
+
JobStatusEvent,
|
|
25
55
|
PromptRequest,
|
|
26
56
|
ServerSentEvent,
|
|
27
57
|
TemplateRequest,
|
|
28
58
|
)
|
|
29
59
|
from rasa.builder.project_generator import ProjectGenerator
|
|
30
|
-
from rasa.builder.
|
|
31
|
-
from rasa.
|
|
32
|
-
from rasa.cli.scaffold import ProjectTemplateName
|
|
60
|
+
from rasa.builder.shared.tracker_context import TrackerContext
|
|
61
|
+
from rasa.core.agent import Agent
|
|
33
62
|
from rasa.core.channels.studio_chat import StudioChatInput
|
|
63
|
+
from rasa.core.exceptions import AgentNotReady
|
|
64
|
+
from rasa.shared.core.flows.flows_list import FlowsList
|
|
65
|
+
from rasa.shared.core.flows.yaml_flows_io import get_flows_as_json
|
|
34
66
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
35
|
-
from rasa.
|
|
67
|
+
from rasa.shared.importers.utils import DOMAIN_KEYS
|
|
68
|
+
from rasa.utils.json_utils import extract_values
|
|
36
69
|
from rasa.utils.openapi import model_to_schema
|
|
37
70
|
|
|
38
71
|
structlogger = structlog.get_logger()
|
|
@@ -73,34 +106,24 @@ def get_input_channel(request: Request) -> StudioChatInput:
|
|
|
73
106
|
return request.app.ctx.input_channel
|
|
74
107
|
|
|
75
108
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
) -> CALMUserData:
|
|
79
|
-
"""Extract CALMUserData from a ProjectGenerator.
|
|
109
|
+
async def extract_bot_data_from_agent(agent: Agent) -> BotData:
|
|
110
|
+
"""Extract BotData from an Agent.
|
|
80
111
|
|
|
81
112
|
Args:
|
|
82
|
-
|
|
113
|
+
agent: The agent to extract data from
|
|
83
114
|
|
|
84
115
|
Returns:
|
|
85
|
-
|
|
116
|
+
BotData containing flows, domain, config, endpoints, and nlu data
|
|
86
117
|
"""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
else:
|
|
97
|
-
endpoints = {}
|
|
98
|
-
|
|
99
|
-
# Use the shared function with the importer and project data paths
|
|
100
|
-
return extract_calm_import_parts_from_importer(
|
|
101
|
-
importer=importer,
|
|
102
|
-
config=None, # Let the shared function get config from importer
|
|
103
|
-
endpoints=endpoints,
|
|
118
|
+
domain = agent.domain.as_dict() if agent.domain else {}
|
|
119
|
+
flows = (
|
|
120
|
+
await agent.processor.get_flows()
|
|
121
|
+
if agent.processor
|
|
122
|
+
else FlowsList(underlying_flows=[])
|
|
123
|
+
)
|
|
124
|
+
return BotData(
|
|
125
|
+
flows=get_flows_as_json(flows),
|
|
126
|
+
domain=extract_values(domain, DOMAIN_KEYS),
|
|
104
127
|
)
|
|
105
128
|
|
|
106
129
|
|
|
@@ -115,26 +138,111 @@ async def health(request: Request) -> HTTPResponse:
|
|
|
115
138
|
return response.json({"status": "ok", "service": "bot-builder"})
|
|
116
139
|
|
|
117
140
|
|
|
141
|
+
@bp.route("/job-events/<job_id>", methods=["GET"])
|
|
142
|
+
@openapi.summary("Stream job progress events")
|
|
143
|
+
@openapi.description(
|
|
144
|
+
"Stream server-sent events (SSE) tracking real-time job progress.\n\n"
|
|
145
|
+
"**Connect with:** `Accept: text/event-stream`.\n\n"
|
|
146
|
+
"**SSE Event Example:**\n"
|
|
147
|
+
"```text\n"
|
|
148
|
+
"event: received\n"
|
|
149
|
+
'data: {"status": "received"}\n'
|
|
150
|
+
"```\n\n"
|
|
151
|
+
)
|
|
152
|
+
@openapi.tag("job-events")
|
|
153
|
+
@openapi.parameter(
|
|
154
|
+
"job_id", str, location="path", description="The id of the job to stream events for"
|
|
155
|
+
)
|
|
156
|
+
@openapi.response(
|
|
157
|
+
200,
|
|
158
|
+
{"text/event-stream": str},
|
|
159
|
+
description="Server-sent events stream. See documentation for event types.",
|
|
160
|
+
example=(
|
|
161
|
+
"event: received\n"
|
|
162
|
+
'data: {"status": "received"}\n'
|
|
163
|
+
"\n"
|
|
164
|
+
"event: generating\n"
|
|
165
|
+
'data: {"status": "generating"}\n'
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
@openapi.response(
|
|
169
|
+
404,
|
|
170
|
+
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
171
|
+
description="Unknown job_id: No such job exists.",
|
|
172
|
+
)
|
|
173
|
+
async def job_events(request: Request, job_id: str) -> HTTPResponse:
|
|
174
|
+
try:
|
|
175
|
+
job = job_manager.get_job(job_id)
|
|
176
|
+
if job is None:
|
|
177
|
+
return response.json(
|
|
178
|
+
ApiErrorResponse(
|
|
179
|
+
error="Job not found", details={"job_id": job_id}
|
|
180
|
+
).model_dump(),
|
|
181
|
+
status=404,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
stream = await request.respond(content_type="text/event-stream")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
async for evt in job.event_stream():
|
|
188
|
+
await stream.send(evt.format())
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
# Handle exceptions within the SSE stream context
|
|
191
|
+
capture_exception_with_context(
|
|
192
|
+
exc,
|
|
193
|
+
"bot_builder_service.job_events.streaming_error",
|
|
194
|
+
extra={"job_id": job_id},
|
|
195
|
+
tags={"endpoint": "/api/job-events/<job_id>"},
|
|
196
|
+
)
|
|
197
|
+
# Send error event in SSE format instead of JSON response
|
|
198
|
+
error_event = JobStatusEvent.from_status(
|
|
199
|
+
status=JobStatus.error.value,
|
|
200
|
+
message=f"Failed to stream job events: {exc}",
|
|
201
|
+
).format()
|
|
202
|
+
await stream.send(error_event)
|
|
203
|
+
finally:
|
|
204
|
+
await stream.eof()
|
|
205
|
+
|
|
206
|
+
return stream
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
# This exception handler only applies before stream.respond() is called
|
|
209
|
+
capture_exception_with_context(
|
|
210
|
+
exc,
|
|
211
|
+
"bot_builder_service.job_events.unexpected_error",
|
|
212
|
+
extra={"job_id": job_id},
|
|
213
|
+
tags={"endpoint": "/api/job-events/<job_id>"},
|
|
214
|
+
)
|
|
215
|
+
return response.json(
|
|
216
|
+
ApiErrorResponse(
|
|
217
|
+
error="Failed to stream job events", details={"error": str(exc)}
|
|
218
|
+
).model_dump(),
|
|
219
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
118
223
|
@bp.route("/prompt-to-bot", methods=["POST"])
|
|
119
224
|
@openapi.summary("Generate bot from natural language prompt")
|
|
120
225
|
@openapi.description(
|
|
121
|
-
"Creates a complete conversational AI bot from a natural language prompt "
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
226
|
+
"Creates a complete conversational AI bot from a natural language prompt. "
|
|
227
|
+
"Returns immediately with a job ID. Connect to `/job-events/<job_id>` to "
|
|
228
|
+
"receive server-sent events (SSE) for real-time progress tracking "
|
|
229
|
+
"throughout the bot creation process.\n\n"
|
|
230
|
+
"**SSE Event Flow** (via `/job-events/<job_id>`):\n"
|
|
125
231
|
"1. `received` - Request received by server\n"
|
|
126
232
|
"2. `generating` - Generating bot project files\n"
|
|
127
233
|
"3. `generation_success` - Bot generation completed successfully\n"
|
|
128
234
|
"4. `training` - Training the bot model\n"
|
|
129
235
|
"5. `train_success` - Model training completed\n"
|
|
130
236
|
"6. `done` - Bot creation completed\n\n"
|
|
131
|
-
"**Error Events
|
|
237
|
+
"**Error Events:**\n"
|
|
132
238
|
"- `generation_error` - Failed to generate bot from prompt\n"
|
|
133
239
|
"- `train_error` - Bot generated but training failed\n"
|
|
134
240
|
"- `validation_error` - Generated bot configuration is invalid\n"
|
|
135
241
|
"- `error` - Unexpected error occurred\n\n"
|
|
136
|
-
"**Usage
|
|
137
|
-
"
|
|
242
|
+
"**Usage:**\n"
|
|
243
|
+
"1. Send POST request with Content-Type: application/json\n"
|
|
244
|
+
"2. The response will be a JSON object `{job_id: ...}`\n"
|
|
245
|
+
"3. Connect to `/job-events/<job_id>` for a server-sent event stream of progress."
|
|
138
246
|
)
|
|
139
247
|
@openapi.tag("bot-generation")
|
|
140
248
|
@openapi.body(
|
|
@@ -144,8 +252,8 @@ async def health(request: Request) -> HTTPResponse:
|
|
|
144
252
|
)
|
|
145
253
|
@openapi.response(
|
|
146
254
|
200,
|
|
147
|
-
{"
|
|
148
|
-
description="
|
|
255
|
+
{"application/json": model_to_schema(JobCreateResponse)},
|
|
256
|
+
description="Job created. Poll or subscribe to /job-events/<job_id> for progress.",
|
|
149
257
|
)
|
|
150
258
|
@openapi.response(
|
|
151
259
|
400,
|
|
@@ -157,148 +265,61 @@ async def health(request: Request) -> HTTPResponse:
|
|
|
157
265
|
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
158
266
|
description="Internal server error",
|
|
159
267
|
)
|
|
160
|
-
async def handle_prompt_to_bot(request: Request) ->
|
|
268
|
+
async def handle_prompt_to_bot(request: Request) -> HTTPResponse:
|
|
161
269
|
"""Handle prompt-to-bot generation requests."""
|
|
162
|
-
sse_response = await request.respond(content_type="text/event-stream")
|
|
163
|
-
project_generator = get_project_generator(request)
|
|
164
|
-
input_channel = get_input_channel(request)
|
|
165
|
-
|
|
166
270
|
try:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
prompt_data = PromptRequest(**request.json)
|
|
175
|
-
|
|
176
|
-
# 2. Generating
|
|
177
|
-
await _send_sse_event(
|
|
178
|
-
sse_response,
|
|
179
|
-
ServerSentEvent(event="generating", data={"status": "generating"}),
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
try:
|
|
183
|
-
# Generate project with retries
|
|
184
|
-
bot_files = await project_generator.generate_project_with_retries(
|
|
185
|
-
prompt_data.prompt,
|
|
186
|
-
template=ProjectTemplateName.PLAIN,
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
await _send_sse_event(
|
|
190
|
-
sse_response,
|
|
191
|
-
ServerSentEvent(
|
|
192
|
-
event="generation_success",
|
|
193
|
-
data={"status": "generation_success"},
|
|
194
|
-
),
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
except (ProjectGenerationError, LLMGenerationError) as e:
|
|
198
|
-
await _send_sse_event(
|
|
199
|
-
sse_response,
|
|
200
|
-
ServerSentEvent(
|
|
201
|
-
event="generation_error",
|
|
202
|
-
data={"status": "generation_error", "error": str(e)},
|
|
203
|
-
),
|
|
204
|
-
)
|
|
205
|
-
await sse_response.eof()
|
|
206
|
-
return
|
|
207
|
-
|
|
208
|
-
# 3. Training
|
|
209
|
-
await _send_sse_event(
|
|
210
|
-
sse_response,
|
|
211
|
-
ServerSentEvent(event="training", data={"status": "training"}),
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
try:
|
|
215
|
-
# Train and load agent
|
|
216
|
-
importer = project_generator._create_importer()
|
|
217
|
-
request.app.ctx.agent = await train_and_load_agent(importer)
|
|
218
|
-
|
|
219
|
-
# Update input channel with new agent
|
|
220
|
-
input_channel.agent = request.app.ctx.agent
|
|
221
|
-
|
|
222
|
-
await _send_sse_event(
|
|
223
|
-
sse_response,
|
|
224
|
-
ServerSentEvent(
|
|
225
|
-
event="train_success", data={"status": "train_success"}
|
|
226
|
-
),
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
except TrainingError as e:
|
|
230
|
-
await _send_sse_event(
|
|
231
|
-
sse_response,
|
|
232
|
-
ServerSentEvent(
|
|
233
|
-
event="train_error",
|
|
234
|
-
data={"status": "train_error", "error": str(e)},
|
|
235
|
-
),
|
|
236
|
-
)
|
|
237
|
-
await sse_response.eof()
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
# 4. Done
|
|
241
|
-
await _send_sse_event(
|
|
242
|
-
sse_response,
|
|
243
|
-
ServerSentEvent(
|
|
244
|
-
event="done",
|
|
245
|
-
data={
|
|
246
|
-
"status": "done",
|
|
247
|
-
},
|
|
248
|
-
),
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
structlogger.info(
|
|
252
|
-
"bot_builder_service.prompt_to_bot.success",
|
|
253
|
-
files_generated=list(bot_files.keys()),
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
except ValidationError as e:
|
|
257
|
-
structlogger.error(
|
|
258
|
-
"bot_builder_service.prompt_to_bot.validation_error", error=str(e)
|
|
259
|
-
)
|
|
260
|
-
await _send_sse_event(
|
|
261
|
-
sse_response,
|
|
262
|
-
ServerSentEvent(
|
|
263
|
-
event="validation_error",
|
|
264
|
-
data={"status": "validation_error", "error": str(e)},
|
|
265
|
-
),
|
|
271
|
+
payload = PromptRequest(**request.json)
|
|
272
|
+
except Exception as exc:
|
|
273
|
+
return response.json(
|
|
274
|
+
ApiErrorResponse(
|
|
275
|
+
error="Invalid request", details={"error": str(exc)}
|
|
276
|
+
).model_dump(),
|
|
277
|
+
status=400,
|
|
266
278
|
)
|
|
267
279
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
280
|
+
try:
|
|
281
|
+
# Allocate job and schedule background task
|
|
282
|
+
job = job_manager.create_job()
|
|
283
|
+
request.app.add_task(run_prompt_to_bot_job(request.app, job, payload.prompt))
|
|
284
|
+
return response.json(JobCreateResponse(job_id=job.id).model_dump(), status=200)
|
|
285
|
+
except Exception as exc:
|
|
286
|
+
capture_exception_with_context(
|
|
287
|
+
exc,
|
|
288
|
+
"bot_builder_service.prompt_to_bot.unexpected_error",
|
|
289
|
+
tags={"endpoint": "/api/prompt-to-bot"},
|
|
271
290
|
)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
return response.json(
|
|
292
|
+
ApiErrorResponse(
|
|
293
|
+
error="Failed to create prompt-to-bot job",
|
|
294
|
+
details={"error": str(exc)},
|
|
295
|
+
).model_dump(),
|
|
296
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
275
297
|
)
|
|
276
|
-
finally:
|
|
277
|
-
await sse_response.eof()
|
|
278
298
|
|
|
279
299
|
|
|
280
300
|
@bp.route("/template-to-bot", methods=["POST"])
|
|
281
301
|
@openapi.summary("Generate bot from predefined template")
|
|
282
302
|
@openapi.description(
|
|
283
|
-
"Creates a complete conversational AI bot from a predefined template
|
|
284
|
-
"
|
|
285
|
-
"
|
|
286
|
-
"
|
|
303
|
+
"Creates a complete conversational AI bot from a predefined template. "
|
|
304
|
+
"Returns immediately with a job ID. Connect to `/job-events/<job_id>` to "
|
|
305
|
+
"receive server-sent events (SSE) for real-time progress tracking "
|
|
306
|
+
"throughout the bot creation process.\n\n"
|
|
307
|
+
"**SSE Event Flow** (via `/job-events/<job_id>`):\n"
|
|
287
308
|
"1. `received` - Request received by server\n"
|
|
288
309
|
"2. `generating` - Initializing bot from template\n"
|
|
289
310
|
"3. `generation_success` - Template initialization completed successfully\n"
|
|
290
311
|
"4. `training` - Training the bot model\n"
|
|
291
312
|
"5. `train_success` - Model training completed\n"
|
|
292
313
|
"6. `done` - Bot creation completed\n\n"
|
|
293
|
-
"**Error Events
|
|
314
|
+
"**Error Events:**\n"
|
|
294
315
|
"- `generation_error` - Failed to initialize bot from template\n"
|
|
295
316
|
"- `train_error` - Template loaded but training failed\n"
|
|
296
317
|
"- `validation_error` - Template configuration is invalid\n"
|
|
297
318
|
"- `error` - Unexpected error occurred\n\n"
|
|
298
|
-
"**Usage
|
|
299
|
-
"
|
|
300
|
-
"
|
|
301
|
-
"
|
|
319
|
+
"**Usage:**\n"
|
|
320
|
+
"1. Send POST request with Content-Type: application/json\n"
|
|
321
|
+
"2. The response will be a JSON object `{job_id: ...}`\n"
|
|
322
|
+
"3. Connect to `/job-events/<job_id>` for a server-sent event stream of progress."
|
|
302
323
|
)
|
|
303
324
|
@openapi.tag("bot-generation")
|
|
304
325
|
@openapi.body(
|
|
@@ -308,12 +329,8 @@ async def handle_prompt_to_bot(request: Request) -> None:
|
|
|
308
329
|
)
|
|
309
330
|
@openapi.response(
|
|
310
331
|
200,
|
|
311
|
-
{"
|
|
312
|
-
description="
|
|
313
|
-
example=ServerSentEvent(
|
|
314
|
-
event="generation_success",
|
|
315
|
-
data={"status": "generation_success"},
|
|
316
|
-
).model_dump(),
|
|
332
|
+
{"application/json": model_to_schema(JobCreateResponse)},
|
|
333
|
+
description="Job created. Poll or subscribe to /job-events/<job_id> for progress.",
|
|
317
334
|
)
|
|
318
335
|
@openapi.response(
|
|
319
336
|
400,
|
|
@@ -325,124 +342,38 @@ async def handle_prompt_to_bot(request: Request) -> None:
|
|
|
325
342
|
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
326
343
|
description="Internal server error",
|
|
327
344
|
)
|
|
328
|
-
async def handle_template_to_bot(request: Request) ->
|
|
329
|
-
"""
|
|
330
|
-
sse_response = await request.respond(content_type="text/event-stream")
|
|
331
|
-
project_generator = get_project_generator(request)
|
|
332
|
-
input_channel = get_input_channel(request)
|
|
333
|
-
|
|
345
|
+
async def handle_template_to_bot(request: Request) -> HTTPResponse:
|
|
346
|
+
"""Create a new template-to-bot job and return job_id immediately."""
|
|
334
347
|
try:
|
|
335
|
-
# 1. Received
|
|
336
|
-
await _send_sse_event(
|
|
337
|
-
sse_response,
|
|
338
|
-
ServerSentEvent(event="received", data={"status": "received"}),
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
# Validate request
|
|
342
348
|
template_data = TemplateRequest(**request.json)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
try:
|
|
351
|
-
# Generate project with retries
|
|
352
|
-
project_generator.init_from_template(
|
|
353
|
-
template_data.template_name,
|
|
354
|
-
)
|
|
355
|
-
bot_files = project_generator.get_bot_files()
|
|
356
|
-
|
|
357
|
-
await _send_sse_event(
|
|
358
|
-
sse_response,
|
|
359
|
-
ServerSentEvent(
|
|
360
|
-
event="generation_success",
|
|
361
|
-
data={"status": "generation_success"},
|
|
362
|
-
),
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
except ProjectGenerationError as e:
|
|
366
|
-
await _send_sse_event(
|
|
367
|
-
sse_response,
|
|
368
|
-
ServerSentEvent(
|
|
369
|
-
event="generation_error",
|
|
370
|
-
data={"status": "generation_error", "error": str(e)},
|
|
371
|
-
),
|
|
372
|
-
)
|
|
373
|
-
await sse_response.eof()
|
|
374
|
-
return
|
|
375
|
-
|
|
376
|
-
# 3. Training
|
|
377
|
-
await _send_sse_event(
|
|
378
|
-
sse_response,
|
|
379
|
-
ServerSentEvent(event="training", data={"status": "training"}),
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
try:
|
|
383
|
-
# Train and load agent
|
|
384
|
-
importer = project_generator._create_importer()
|
|
385
|
-
request.app.ctx.agent = await train_and_load_agent(importer)
|
|
386
|
-
|
|
387
|
-
# Update input channel with new agent
|
|
388
|
-
input_channel.agent = request.app.ctx.agent
|
|
389
|
-
|
|
390
|
-
await _send_sse_event(
|
|
391
|
-
sse_response,
|
|
392
|
-
ServerSentEvent(
|
|
393
|
-
event="train_success", data={"status": "train_success"}
|
|
394
|
-
),
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
except TrainingError as e:
|
|
398
|
-
await _send_sse_event(
|
|
399
|
-
sse_response,
|
|
400
|
-
ServerSentEvent(
|
|
401
|
-
event="train_error",
|
|
402
|
-
data={"status": "train_error", "error": str(e)},
|
|
403
|
-
),
|
|
404
|
-
)
|
|
405
|
-
await sse_response.eof()
|
|
406
|
-
return
|
|
407
|
-
|
|
408
|
-
# 4. Done
|
|
409
|
-
await _send_sse_event(
|
|
410
|
-
sse_response,
|
|
411
|
-
ServerSentEvent(
|
|
412
|
-
event="done",
|
|
413
|
-
data={
|
|
414
|
-
"status": "done",
|
|
415
|
-
},
|
|
416
|
-
),
|
|
417
|
-
)
|
|
418
|
-
|
|
419
|
-
structlogger.info(
|
|
420
|
-
"bot_builder_service.template_to_bot.success",
|
|
421
|
-
files_generated=list(bot_files.keys()),
|
|
349
|
+
except Exception as exc:
|
|
350
|
+
return response.json(
|
|
351
|
+
ApiErrorResponse(
|
|
352
|
+
error="Invalid request", details={"error": str(exc)}
|
|
353
|
+
).model_dump(),
|
|
354
|
+
status=400,
|
|
422
355
|
)
|
|
423
356
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
sse_response,
|
|
430
|
-
ServerSentEvent(
|
|
431
|
-
event="validation_error",
|
|
432
|
-
data={"status": "validation_error", "error": str(e)},
|
|
433
|
-
),
|
|
357
|
+
try:
|
|
358
|
+
# allocate job and schedule background task
|
|
359
|
+
job = job_manager.create_job()
|
|
360
|
+
request.app.add_task(
|
|
361
|
+
run_template_to_bot_job(request.app, job, template_data.template_name)
|
|
434
362
|
)
|
|
435
|
-
|
|
436
|
-
except Exception as
|
|
437
|
-
|
|
438
|
-
|
|
363
|
+
return response.json(JobCreateResponse(job_id=job.id).model_dump(), status=200)
|
|
364
|
+
except Exception as exc:
|
|
365
|
+
capture_exception_with_context(
|
|
366
|
+
exc,
|
|
367
|
+
"bot_builder_service.template_to_bot.unexpected_error",
|
|
368
|
+
tags={"endpoint": "/api/template-to-bot"},
|
|
439
369
|
)
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
370
|
+
return response.json(
|
|
371
|
+
ApiErrorResponse(
|
|
372
|
+
error="Failed to create template-to-bot job",
|
|
373
|
+
details={"error": str(exc)},
|
|
374
|
+
).model_dump(),
|
|
375
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
443
376
|
)
|
|
444
|
-
finally:
|
|
445
|
-
await sse_response.eof()
|
|
446
377
|
|
|
447
378
|
|
|
448
379
|
@bp.route("/files", methods=["GET"])
|
|
@@ -461,18 +392,32 @@ async def handle_template_to_bot(request: Request) -> None:
|
|
|
461
392
|
)
|
|
462
393
|
async def get_bot_files(request: Request) -> HTTPResponse:
|
|
463
394
|
"""Get current bot files."""
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
395
|
+
try:
|
|
396
|
+
project_generator = get_project_generator(request)
|
|
397
|
+
bot_files = project_generator.get_bot_files()
|
|
398
|
+
return response.json(bot_files)
|
|
399
|
+
except Exception as exc:
|
|
400
|
+
capture_exception_with_context(
|
|
401
|
+
exc,
|
|
402
|
+
"bot_builder_service.get_bot_files.unexpected_error",
|
|
403
|
+
tags={"endpoint": "/api/files", "method": "GET"},
|
|
404
|
+
)
|
|
405
|
+
return response.json(
|
|
406
|
+
ApiErrorResponse(
|
|
407
|
+
error="Failed to retrieve bot files", details={"error": str(exc)}
|
|
408
|
+
).model_dump(),
|
|
409
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
410
|
+
)
|
|
467
411
|
|
|
468
412
|
|
|
469
413
|
@bp.route("/files", methods=["PUT"])
|
|
470
414
|
@openapi.summary("Update bot files")
|
|
471
415
|
@openapi.description(
|
|
472
416
|
"Updates the bot configuration files and retrains the model. "
|
|
473
|
-
"Returns
|
|
474
|
-
"
|
|
475
|
-
"
|
|
417
|
+
"Returns immediately with a job ID. Connect to `/job-events/<job_id>` "
|
|
418
|
+
"for real-time SSE progress tracking."
|
|
419
|
+
"\n\n"
|
|
420
|
+
"**SSE Event Flow:** (available via /job-events/<job_id>)\n"
|
|
476
421
|
"1. `received` - Request received by server\n"
|
|
477
422
|
"2. `validating` - Validating bot configuration files\n"
|
|
478
423
|
"3. `validation_success` - File validation completed successfully\n"
|
|
@@ -483,8 +428,10 @@ async def get_bot_files(request: Request) -> HTTPResponse:
|
|
|
483
428
|
"- `validation_error` - Bot configuration files are invalid\n"
|
|
484
429
|
"- `train_error` - Files updated but training failed\n"
|
|
485
430
|
"- `error` - Unexpected error occurred\n\n"
|
|
486
|
-
"**Usage
|
|
487
|
-
"
|
|
431
|
+
"**Usage:**\n"
|
|
432
|
+
"1. Send PUT request with Content-Type: application/json\n"
|
|
433
|
+
"2. The response will be a JSON object `{job_id: ...}`\n"
|
|
434
|
+
"3. Connect to `/job-events/<job_id>` for a server-sent event stream of progress."
|
|
488
435
|
)
|
|
489
436
|
@openapi.tag("bot-files")
|
|
490
437
|
@openapi.body(
|
|
@@ -496,8 +443,11 @@ async def get_bot_files(request: Request) -> HTTPResponse:
|
|
|
496
443
|
)
|
|
497
444
|
@openapi.response(
|
|
498
445
|
200,
|
|
499
|
-
{"
|
|
500
|
-
description=
|
|
446
|
+
{"application/json": model_to_schema(JobCreateResponse)},
|
|
447
|
+
description=(
|
|
448
|
+
"Job created. Poll or subscribe to /job-events/<job_id> "
|
|
449
|
+
"for progress and SSE updates."
|
|
450
|
+
),
|
|
501
451
|
)
|
|
502
452
|
@openapi.response(
|
|
503
453
|
400,
|
|
@@ -509,101 +459,34 @@ async def get_bot_files(request: Request) -> HTTPResponse:
|
|
|
509
459
|
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
510
460
|
description="Internal server error",
|
|
511
461
|
)
|
|
512
|
-
async def update_bot_files(request: Request) ->
|
|
462
|
+
async def update_bot_files(request: Request) -> HTTPResponse:
|
|
513
463
|
"""Update bot files with server-sent events for progress tracking."""
|
|
514
|
-
sse_response = await request.respond(content_type="text/event-stream")
|
|
515
|
-
project_generator = get_project_generator(request)
|
|
516
|
-
input_channel = get_input_channel(request)
|
|
517
|
-
|
|
518
464
|
try:
|
|
519
|
-
# 1. Received
|
|
520
|
-
await _send_sse_event(
|
|
521
|
-
sse_response,
|
|
522
|
-
ServerSentEvent(event="received", data={"status": "received"}),
|
|
523
|
-
)
|
|
524
|
-
|
|
525
|
-
# Update bot files
|
|
526
465
|
bot_files = request.json
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
)
|
|
534
|
-
|
|
535
|
-
try:
|
|
536
|
-
importer = project_generator._create_importer()
|
|
537
|
-
validation_error = await validate_project(importer)
|
|
538
|
-
|
|
539
|
-
if validation_error:
|
|
540
|
-
raise ValidationError(validation_error)
|
|
541
|
-
|
|
542
|
-
await _send_sse_event(
|
|
543
|
-
sse_response,
|
|
544
|
-
ServerSentEvent(
|
|
545
|
-
event="validation_success",
|
|
546
|
-
data={"status": "validation_success"},
|
|
547
|
-
),
|
|
548
|
-
)
|
|
549
|
-
|
|
550
|
-
except ValidationError as e:
|
|
551
|
-
await _send_sse_event(
|
|
552
|
-
sse_response,
|
|
553
|
-
ServerSentEvent(
|
|
554
|
-
event="validation_error",
|
|
555
|
-
data={"status": "validation_error", "error": str(e)},
|
|
556
|
-
),
|
|
557
|
-
)
|
|
558
|
-
await sse_response.eof()
|
|
559
|
-
return
|
|
560
|
-
|
|
561
|
-
# 3. Training
|
|
562
|
-
await _send_sse_event(
|
|
563
|
-
sse_response,
|
|
564
|
-
ServerSentEvent(event="training", data={"status": "training"}),
|
|
466
|
+
except Exception as exc:
|
|
467
|
+
return response.json(
|
|
468
|
+
ApiErrorResponse(
|
|
469
|
+
error="Invalid request", details={"error": str(exc)}
|
|
470
|
+
).model_dump(),
|
|
471
|
+
status=400,
|
|
565
472
|
)
|
|
566
473
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
except TrainingError as e:
|
|
579
|
-
await _send_sse_event(
|
|
580
|
-
sse_response,
|
|
581
|
-
ServerSentEvent(
|
|
582
|
-
event="train_error",
|
|
583
|
-
data={"status": "train_error", "error": str(e)},
|
|
584
|
-
),
|
|
585
|
-
)
|
|
586
|
-
await sse_response.eof()
|
|
587
|
-
return
|
|
588
|
-
|
|
589
|
-
# 4. Done
|
|
590
|
-
await _send_sse_event(
|
|
591
|
-
sse_response,
|
|
592
|
-
ServerSentEvent(
|
|
593
|
-
event="done",
|
|
594
|
-
data={
|
|
595
|
-
"status": "done",
|
|
596
|
-
},
|
|
597
|
-
),
|
|
474
|
+
try:
|
|
475
|
+
job = job_manager.create_job()
|
|
476
|
+
request.app.add_task(run_update_files_job(request.app, job, bot_files))
|
|
477
|
+
return response.json(JobCreateResponse(job_id=job.id).model_dump(), status=200)
|
|
478
|
+
except Exception as exc:
|
|
479
|
+
capture_exception_with_context(
|
|
480
|
+
exc,
|
|
481
|
+
"bot_builder_service.update_bot_files.unexpected_error",
|
|
482
|
+
tags={"endpoint": "/api/files", "method": "PUT"},
|
|
598
483
|
)
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
484
|
+
return response.json(
|
|
485
|
+
ApiErrorResponse(
|
|
486
|
+
error="Failed to update bot files", details={"error": str(exc)}
|
|
487
|
+
).model_dump(),
|
|
488
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
604
489
|
)
|
|
605
|
-
finally:
|
|
606
|
-
await sse_response.eof()
|
|
607
490
|
|
|
608
491
|
|
|
609
492
|
@bp.route("/data", methods=["GET"])
|
|
@@ -615,9 +498,14 @@ async def update_bot_files(request: Request) -> None:
|
|
|
615
498
|
@openapi.tag("bot-data")
|
|
616
499
|
@openapi.response(
|
|
617
500
|
200,
|
|
618
|
-
{"application/json": model_to_schema(
|
|
501
|
+
{"application/json": model_to_schema(BotData)},
|
|
619
502
|
description="Bot data retrieved successfully",
|
|
620
503
|
)
|
|
504
|
+
@openapi.response(
|
|
505
|
+
409,
|
|
506
|
+
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
507
|
+
description="Agent not ready",
|
|
508
|
+
)
|
|
621
509
|
@openapi.response(
|
|
622
510
|
500,
|
|
623
511
|
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
@@ -626,17 +514,196 @@ async def update_bot_files(request: Request) -> None:
|
|
|
626
514
|
async def get_bot_data(request: Request) -> HTTPResponse:
|
|
627
515
|
"""Get current bot data in CALM import format."""
|
|
628
516
|
try:
|
|
629
|
-
|
|
630
|
-
|
|
517
|
+
agent: Optional[Agent] = request.app.ctx.agent
|
|
518
|
+
if not agent:
|
|
519
|
+
raise AgentNotReady(
|
|
520
|
+
"Can't retrieve the data without an agent being loaded."
|
|
521
|
+
)
|
|
631
522
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
523
|
+
bot_data = await extract_bot_data_from_agent(agent)
|
|
524
|
+
|
|
525
|
+
return response.json(bot_data.model_dump())
|
|
526
|
+
except AgentNotReady as e:
|
|
527
|
+
return response.json(
|
|
528
|
+
ApiErrorResponse(
|
|
529
|
+
error="Agent not ready",
|
|
530
|
+
details={"error": str(e)},
|
|
531
|
+
).model_dump(),
|
|
532
|
+
status=HTTPStatus.CONFLICT,
|
|
533
|
+
)
|
|
534
|
+
except Exception as exc:
|
|
535
|
+
capture_exception_with_context(
|
|
536
|
+
exc,
|
|
537
|
+
"bot_builder_service.get_bot_data.unexpected_error",
|
|
538
|
+
tags={"endpoint": "/api/data"},
|
|
539
|
+
)
|
|
635
540
|
return response.json(
|
|
636
541
|
ApiErrorResponse(
|
|
637
542
|
error="Failed to retrieve bot data",
|
|
543
|
+
details={"error": str(exc)},
|
|
544
|
+
).model_dump(),
|
|
545
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
@bp.route("/assistant", methods=["GET"])
|
|
550
|
+
@openapi.summary("Get assistant info")
|
|
551
|
+
@openapi.description(
|
|
552
|
+
"Returns basic information about the loaded assistant, including the assistant id "
|
|
553
|
+
"as configured in the model's metadata (from config.yml)."
|
|
554
|
+
)
|
|
555
|
+
@openapi.tag("assistant")
|
|
556
|
+
@openapi.response(
|
|
557
|
+
200,
|
|
558
|
+
{"application/json": model_to_schema(AssistantInfo)},
|
|
559
|
+
description="Assistant info retrieved successfully",
|
|
560
|
+
)
|
|
561
|
+
@openapi.response(
|
|
562
|
+
409,
|
|
563
|
+
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
564
|
+
description="Agent not ready",
|
|
565
|
+
)
|
|
566
|
+
@openapi.response(
|
|
567
|
+
500,
|
|
568
|
+
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
569
|
+
description="Internal server error",
|
|
570
|
+
)
|
|
571
|
+
async def get_bot_info(request: Request) -> HTTPResponse:
|
|
572
|
+
"""Return assistant info including assistant id from model metadata."""
|
|
573
|
+
try:
|
|
574
|
+
agent: Optional[Agent] = request.app.ctx.agent
|
|
575
|
+
if not agent:
|
|
576
|
+
raise AgentNotReady("Can't retrieve bot info without a loaded agent.")
|
|
577
|
+
|
|
578
|
+
assistant_id: Optional[str] = (
|
|
579
|
+
agent.processor.model_metadata.assistant_id
|
|
580
|
+
if agent.processor
|
|
581
|
+
and agent.processor.model_metadata
|
|
582
|
+
and hasattr(agent.processor.model_metadata, "assistant_id")
|
|
583
|
+
else None
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
return response.json(AssistantInfo(assistant_id=assistant_id).model_dump())
|
|
587
|
+
except AgentNotReady as e:
|
|
588
|
+
return response.json(
|
|
589
|
+
ApiErrorResponse(
|
|
590
|
+
error="Agent not ready",
|
|
638
591
|
details={"error": str(e)},
|
|
639
592
|
).model_dump(),
|
|
593
|
+
status=HTTPStatus.CONFLICT,
|
|
594
|
+
)
|
|
595
|
+
except Exception as exc:
|
|
596
|
+
capture_exception_with_context(
|
|
597
|
+
exc,
|
|
598
|
+
"bot_builder_service.get_bot_info.unexpected_error",
|
|
599
|
+
tags={"endpoint": "/api/assistant"},
|
|
600
|
+
)
|
|
601
|
+
return response.json(
|
|
602
|
+
ApiErrorResponse(
|
|
603
|
+
error="Failed to retrieve bot info",
|
|
604
|
+
details={"error": str(exc)},
|
|
605
|
+
).model_dump(),
|
|
606
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
@bp.route("/download", methods=["GET"])
|
|
611
|
+
@openapi.summary("Download bot project as tar.gz")
|
|
612
|
+
@openapi.description(
|
|
613
|
+
"Downloads the current bot project files as a compressed tar.gz archive. "
|
|
614
|
+
"Includes all configuration files and a .env file with RASA_PRO_LICENSE. "
|
|
615
|
+
"Requires valid JWT token in Authorization header."
|
|
616
|
+
)
|
|
617
|
+
@openapi.tag("bot-files")
|
|
618
|
+
@openapi.parameter(
|
|
619
|
+
"Authorization",
|
|
620
|
+
description="Bearer token for authentication",
|
|
621
|
+
_in="header",
|
|
622
|
+
required=True,
|
|
623
|
+
schema=str,
|
|
624
|
+
)
|
|
625
|
+
@openapi.parameter(
|
|
626
|
+
"project_name",
|
|
627
|
+
description="Name of the project for the archive filename and pyproject.toml",
|
|
628
|
+
_in="query",
|
|
629
|
+
required=False,
|
|
630
|
+
schema=str,
|
|
631
|
+
)
|
|
632
|
+
@openapi.response(
|
|
633
|
+
200,
|
|
634
|
+
{"application/gzip": bytes},
|
|
635
|
+
description="Bot project downloaded successfully as tar.gz",
|
|
636
|
+
)
|
|
637
|
+
@openapi.response(
|
|
638
|
+
401,
|
|
639
|
+
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
640
|
+
description="Authentication failed - invalid or missing token",
|
|
641
|
+
)
|
|
642
|
+
@openapi.response(
|
|
643
|
+
500,
|
|
644
|
+
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
645
|
+
description="Internal server error",
|
|
646
|
+
)
|
|
647
|
+
async def download_bot_project(request: Request) -> HTTPResponse:
|
|
648
|
+
"""Download bot project as tar.gz archive."""
|
|
649
|
+
try:
|
|
650
|
+
# Extract and verify token
|
|
651
|
+
auth_header = request.headers.get("Authorization", "")
|
|
652
|
+
verification_result = extract_and_verify_auth0_token(auth_header)
|
|
653
|
+
|
|
654
|
+
if verification_result.error_message is not None:
|
|
655
|
+
structlogger.error(
|
|
656
|
+
"bot_builder_service.download_bot_project.token_verification_failed",
|
|
657
|
+
error=verification_result.error_message,
|
|
658
|
+
)
|
|
659
|
+
return response.json(
|
|
660
|
+
ApiErrorResponse(
|
|
661
|
+
error=verification_result.error_message,
|
|
662
|
+
details={"expected": "Bearer <valid_token>"},
|
|
663
|
+
).model_dump(),
|
|
664
|
+
status=401,
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
# Get bot files
|
|
668
|
+
project_generator = get_project_generator(request)
|
|
669
|
+
bot_files = project_generator.get_bot_files()
|
|
670
|
+
|
|
671
|
+
# Get project name from query parameters, default to "bot-project"
|
|
672
|
+
project_name = request.args.get("project_name", "bot-project")
|
|
673
|
+
|
|
674
|
+
# Create tar.gz archive
|
|
675
|
+
tar_data = create_bot_project_archive(bot_files, project_name)
|
|
676
|
+
|
|
677
|
+
structlogger.info(
|
|
678
|
+
"bot_builder_service.download_bot_project.success",
|
|
679
|
+
user_sub=verification_result.payload.get("sub")
|
|
680
|
+
if verification_result.payload
|
|
681
|
+
else None,
|
|
682
|
+
files_count=len(bot_files),
|
|
683
|
+
archive_size=len(tar_data),
|
|
684
|
+
payload=verification_result.payload,
|
|
685
|
+
project_name=project_name,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
return response.raw(
|
|
689
|
+
tar_data,
|
|
690
|
+
content_type="application/gzip",
|
|
691
|
+
headers={
|
|
692
|
+
"Content-Disposition": f"attachment; filename={project_name}.tar.gz"
|
|
693
|
+
},
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
except Exception as exc:
|
|
697
|
+
capture_exception_with_context(
|
|
698
|
+
exc,
|
|
699
|
+
"bot_builder_service.download_bot_project.unexpected_error",
|
|
700
|
+
tags={"endpoint": "/api/download"},
|
|
701
|
+
)
|
|
702
|
+
return response.json(
|
|
703
|
+
ApiErrorResponse(
|
|
704
|
+
error="Failed to create bot project archive",
|
|
705
|
+
details={"error": str(exc)},
|
|
706
|
+
).model_dump(),
|
|
640
707
|
status=500,
|
|
641
708
|
)
|
|
642
709
|
|
|
@@ -644,8 +711,10 @@ async def get_bot_data(request: Request) -> HTTPResponse:
|
|
|
644
711
|
@bp.route("/copilot", methods=["POST"])
|
|
645
712
|
@openapi.summary("AI copilot for bot building")
|
|
646
713
|
@openapi.description(
|
|
647
|
-
"Provides LLM-powered copilot assistance
|
|
648
|
-
"
|
|
714
|
+
"Provides LLM-powered copilot assistance with streaming markdown responses. "
|
|
715
|
+
"Returns server-sent events (SSE) for real-time streaming of copilot responses.\n\n"
|
|
716
|
+
"The event's `event` field is the type of event. For this endpoint it will always "
|
|
717
|
+
"be: `copilot_response`.\n\n"
|
|
649
718
|
)
|
|
650
719
|
@openapi.tag("copilot")
|
|
651
720
|
@openapi.body(
|
|
@@ -653,15 +722,74 @@ async def get_bot_data(request: Request) -> HTTPResponse:
|
|
|
653
722
|
description=(
|
|
654
723
|
"Copilot request containing: "
|
|
655
724
|
"1. conversation history between user and copilot, "
|
|
656
|
-
"2. session ID for tracking conversation context with the bot being built
|
|
657
|
-
"3. additional context."
|
|
725
|
+
"2. session ID for tracking conversation context with the bot being built."
|
|
658
726
|
),
|
|
659
727
|
required=True,
|
|
660
728
|
)
|
|
661
729
|
@openapi.response(
|
|
662
730
|
200,
|
|
663
|
-
{"
|
|
664
|
-
description=
|
|
731
|
+
{"text/event-stream": model_to_schema(ServerSentEvent)},
|
|
732
|
+
description=(
|
|
733
|
+
"Server-sent events stream with copilot responses and references. "
|
|
734
|
+
"The event's `event` field is the type "
|
|
735
|
+
"of event. For this endpoint it will always be: `copilot_response`. "
|
|
736
|
+
"The event's data field is a JSON dump. The following describes the event data "
|
|
737
|
+
"field:\n"
|
|
738
|
+
"- `response_category` can be one of the following:\n"
|
|
739
|
+
" - `copilot` - Stream token generated by the copilot.\n"
|
|
740
|
+
" - `out_of_scope_detection` - Response coming from the the out of scope detection.\n" # noqa: E501
|
|
741
|
+
" - `roleplay_detection` - Response coming from the the roleplay detection.\n"
|
|
742
|
+
" - `reference` - Reference section.\n"
|
|
743
|
+
"- `completeness`: Whether this is a streaming token or complete response.\n"
|
|
744
|
+
"- `content`: The actual response content (for streaming tokens).\n"
|
|
745
|
+
"- `references`: (Only for `reference` response category) List of reference entries. Each reference entry is a dictionary with the following keys:\n" # noqa: E501
|
|
746
|
+
" - `index`: Reference index as an integer number.\n"
|
|
747
|
+
" - `title`: Reference title as a string.\n"
|
|
748
|
+
" - `url`: Reference URL as a string.\n\n"
|
|
749
|
+
),
|
|
750
|
+
examples={
|
|
751
|
+
"Streaming token from copilot": {
|
|
752
|
+
"summary": "Streaming token from copilot",
|
|
753
|
+
"value": GeneratedContent(
|
|
754
|
+
content="<token generated by the copilot>",
|
|
755
|
+
response_category=ResponseCategory.COPILOT,
|
|
756
|
+
response_completeness=ResponseCompleteness.TOKEN,
|
|
757
|
+
)
|
|
758
|
+
.to_sse_event()
|
|
759
|
+
.model_dump(),
|
|
760
|
+
},
|
|
761
|
+
"Complete response from out of scope / roleplay detection / guardrail policy violation": { # noqa: E501
|
|
762
|
+
"summary": "Complete response from copilot",
|
|
763
|
+
"value": GeneratedContent(
|
|
764
|
+
content="<response from the out of scope detection>",
|
|
765
|
+
response_category=ResponseCategory.OUT_OF_SCOPE_DETECTION,
|
|
766
|
+
response_completeness=ResponseCompleteness.COMPLETE,
|
|
767
|
+
)
|
|
768
|
+
.to_sse_event()
|
|
769
|
+
.model_dump(),
|
|
770
|
+
},
|
|
771
|
+
"Response with the references": {
|
|
772
|
+
"summary": "Reference section with all references",
|
|
773
|
+
"value": ReferenceSection(
|
|
774
|
+
references=[
|
|
775
|
+
ReferenceEntry(
|
|
776
|
+
index=1,
|
|
777
|
+
title="Title of the reference",
|
|
778
|
+
url="https://rasa.com/docs/...",
|
|
779
|
+
),
|
|
780
|
+
ReferenceEntry(
|
|
781
|
+
index=2,
|
|
782
|
+
title="Title of another reference",
|
|
783
|
+
url="https://rasa.com/docs/...",
|
|
784
|
+
),
|
|
785
|
+
],
|
|
786
|
+
response_category=ResponseCategory.REFERENCE,
|
|
787
|
+
response_completeness=ResponseCompleteness.COMPLETE,
|
|
788
|
+
)
|
|
789
|
+
.to_sse_event()
|
|
790
|
+
.model_dump(),
|
|
791
|
+
},
|
|
792
|
+
},
|
|
665
793
|
)
|
|
666
794
|
@openapi.response(
|
|
667
795
|
400,
|
|
@@ -678,71 +806,135 @@ async def get_bot_data(request: Request) -> HTTPResponse:
|
|
|
678
806
|
{"application/json": model_to_schema(ApiErrorResponse)},
|
|
679
807
|
description="Internal server error.",
|
|
680
808
|
)
|
|
681
|
-
async def copilot(request: Request) ->
|
|
682
|
-
"""Handle copilot requests."""
|
|
809
|
+
async def copilot(request: Request) -> None:
|
|
810
|
+
"""Handle copilot requests with streaming markdown responses."""
|
|
811
|
+
sse = await request.respond(content_type="text/event-stream")
|
|
683
812
|
project_generator = get_project_generator(request)
|
|
684
|
-
input_channel = get_input_channel(request)
|
|
685
813
|
|
|
686
814
|
try:
|
|
687
|
-
# Validate
|
|
688
|
-
|
|
815
|
+
# 1. Validate and unpack input
|
|
816
|
+
req = CopilotRequest(**request.json)
|
|
689
817
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
request.
|
|
818
|
+
telemetry = CopilotTelemetry(
|
|
819
|
+
project_id=HELLO_RASA_PROJECT_ID,
|
|
820
|
+
user_id=request.headers.get(HEADER_USER_ID),
|
|
693
821
|
)
|
|
694
|
-
|
|
695
|
-
|
|
822
|
+
structlogger.debug("builder.copilot.telemetry.request.init")
|
|
823
|
+
|
|
824
|
+
if req.last_message and req.last_message.role == ROLE_USER:
|
|
825
|
+
structlogger.debug("builder.copilot.telemetry.request.user_turn")
|
|
826
|
+
# Offload telemetry logging to a background task
|
|
827
|
+
request.app.add_task(
|
|
828
|
+
asyncio.to_thread(
|
|
829
|
+
telemetry.log_user_turn, req.last_message.get_text_content()
|
|
830
|
+
)
|
|
831
|
+
)
|
|
696
832
|
|
|
697
|
-
#
|
|
833
|
+
# 2. Get the necessary context for the copilot
|
|
834
|
+
tracker = await current_tracker_from_input_channel(request.app, req.session_id)
|
|
835
|
+
tracker_context = TrackerContext.from_tracker(
|
|
836
|
+
tracker, max_turns=COPILOT_ASSISTANT_TRACKER_MAX_TURNS
|
|
837
|
+
)
|
|
838
|
+
tracker_context = await check_assistant_chat_for_policy_violations(
|
|
839
|
+
tracker_context=tracker_context,
|
|
840
|
+
hello_rasa_user_id=request.headers.get(HEADER_USER_ID),
|
|
841
|
+
hello_rasa_project_id=HELLO_RASA_PROJECT_ID,
|
|
842
|
+
)
|
|
698
843
|
context = CopilotContext(
|
|
699
|
-
|
|
700
|
-
assistant_logs=
|
|
701
|
-
assistant_files=
|
|
702
|
-
copilot_chat_history=
|
|
844
|
+
tracker_context=tracker_context,
|
|
845
|
+
assistant_logs=get_recent_logs(),
|
|
846
|
+
assistant_files=project_generator.get_bot_files(),
|
|
847
|
+
copilot_chat_history=req.copilot_chat_history,
|
|
703
848
|
)
|
|
704
849
|
|
|
705
|
-
#
|
|
706
|
-
|
|
850
|
+
# 3. Run guardrail policy checks. If any policy violations are detected,
|
|
851
|
+
# send a response and end the stream.
|
|
852
|
+
guardrail_response: Optional[
|
|
853
|
+
GeneratedContent
|
|
854
|
+
] = await check_copilot_chat_for_policy_violations(
|
|
855
|
+
context=context,
|
|
856
|
+
hello_rasa_user_id=request.headers.get(HEADER_USER_ID),
|
|
857
|
+
hello_rasa_project_id=HELLO_RASA_PROJECT_ID,
|
|
858
|
+
)
|
|
859
|
+
if guardrail_response is not None:
|
|
860
|
+
await sse.send(guardrail_response.to_sse_event().format())
|
|
861
|
+
await sse.eof()
|
|
862
|
+
return
|
|
707
863
|
|
|
708
|
-
|
|
864
|
+
# 4. Get the original response stream from copilot and handle it with the
|
|
865
|
+
# copilot response handler
|
|
866
|
+
start_timestamp = time.perf_counter()
|
|
867
|
+
copilot_client = llm_service.instantiate_copilot()
|
|
868
|
+
(
|
|
869
|
+
original_stream,
|
|
870
|
+
used_documents,
|
|
871
|
+
) = await copilot_client.generate_response(context)
|
|
872
|
+
|
|
873
|
+
copilot_response_handler = llm_service.instantiate_handler(
|
|
874
|
+
COPILOT_HANDLER_ROLLING_BUFFER_SIZE
|
|
875
|
+
)
|
|
876
|
+
intercepted_stream = copilot_response_handler.handle_response(original_stream)
|
|
877
|
+
|
|
878
|
+
# 5. Stream the intercepted response
|
|
879
|
+
async for token in intercepted_stream:
|
|
880
|
+
await sse.send(token.to_sse_event().format())
|
|
881
|
+
|
|
882
|
+
# Offload telemetry logging to a background task
|
|
883
|
+
request.app.add_task(
|
|
884
|
+
asyncio.to_thread(
|
|
885
|
+
telemetry.log_copilot_from_handler,
|
|
886
|
+
handler=copilot_response_handler,
|
|
887
|
+
used_documents=used_documents,
|
|
888
|
+
latency_ms=int((time.perf_counter() - start_timestamp) * 1000),
|
|
889
|
+
**copilot_client.usage_statistics.model_dump(),
|
|
890
|
+
)
|
|
891
|
+
)
|
|
709
892
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
893
|
+
# 6. Once the stream is over, extract and send references
|
|
894
|
+
# if any documents were used
|
|
895
|
+
if used_documents:
|
|
896
|
+
reference_section = copilot_response_handler.extract_references(
|
|
897
|
+
used_documents
|
|
898
|
+
)
|
|
899
|
+
await sse.send(reference_section.to_sse_event().format())
|
|
900
|
+
|
|
901
|
+
except CopilotStreamError as e:
|
|
902
|
+
capture_exception_with_context(
|
|
903
|
+
e,
|
|
904
|
+
"bot_builder_service.copilot.generation_error",
|
|
905
|
+
extra={"session_id": req.session_id},
|
|
906
|
+
tags={"endpoint": "/api/copilot"},
|
|
713
907
|
)
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
908
|
+
await sse.send(
|
|
909
|
+
ServerSentEvent(
|
|
910
|
+
event="error",
|
|
911
|
+
data={"error": str(e)},
|
|
912
|
+
).format()
|
|
719
913
|
)
|
|
720
914
|
|
|
721
|
-
except Exception as
|
|
722
|
-
|
|
723
|
-
|
|
915
|
+
except Exception as exc:
|
|
916
|
+
capture_exception_with_context(
|
|
917
|
+
exc,
|
|
918
|
+
"bot_builder_service.copilot.unexpected_error",
|
|
919
|
+
extra={"session_id": req.session_id if "req" in locals() else None},
|
|
920
|
+
tags={"endpoint": "/api/copilot"},
|
|
724
921
|
)
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
).
|
|
730
|
-
status=500,
|
|
922
|
+
await sse.send(
|
|
923
|
+
ServerSentEvent(
|
|
924
|
+
event="error",
|
|
925
|
+
data={"error": str(exc)},
|
|
926
|
+
).format()
|
|
731
927
|
)
|
|
732
928
|
|
|
929
|
+
finally:
|
|
930
|
+
await sse.eof()
|
|
931
|
+
|
|
733
932
|
|
|
734
933
|
async def current_tracker_from_input_channel(
|
|
735
|
-
app: Any,
|
|
934
|
+
app: Any, session_id: str
|
|
736
935
|
) -> Optional[DialogueStateTracker]:
|
|
737
936
|
"""Generate chat bot context from current conversation."""
|
|
738
|
-
if app.ctx.agent and
|
|
739
|
-
return await app.ctx.agent.tracker_store.retrieve(
|
|
740
|
-
input_channel.latest_tracker_session_id
|
|
741
|
-
)
|
|
937
|
+
if app.ctx.agent and session_id:
|
|
938
|
+
return await app.ctx.agent.tracker_store.retrieve(session_id)
|
|
742
939
|
else:
|
|
743
940
|
return None
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
async def _send_sse_event(sse_response: HTTPResponse, event: ServerSentEvent) -> None:
|
|
747
|
-
"""Send a server-sent event."""
|
|
748
|
-
await sse_response.send(event.format())
|