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.

Files changed (275) hide show
  1. rasa/__main__.py +8 -0
  2. rasa/builder/auth.py +71 -0
  3. rasa/builder/config.py +16 -0
  4. rasa/builder/copilot/constants.py +15 -0
  5. rasa/builder/copilot/copilot.py +342 -0
  6. rasa/builder/copilot/copilot_response_handler.py +471 -0
  7. rasa/builder/copilot/exceptions.py +20 -0
  8. rasa/builder/copilot/models.py +344 -0
  9. rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +495 -0
  10. rasa/builder/copilot/telemetry.py +195 -0
  11. rasa/builder/document_retrieval/__init__.py +0 -0
  12. rasa/builder/document_retrieval/constants.py +16 -0
  13. rasa/builder/{inkeep_document_retrieval.py → document_retrieval/inkeep_document_retrieval.py} +24 -30
  14. rasa/builder/document_retrieval/models.py +62 -0
  15. rasa/builder/download.py +140 -0
  16. rasa/builder/guardrails/__init__.py +1 -0
  17. rasa/builder/guardrails/constants.py +4 -0
  18. rasa/builder/guardrails/exceptions.py +4 -0
  19. rasa/builder/guardrails/lakera.py +188 -0
  20. rasa/builder/guardrails/models.py +199 -0
  21. rasa/builder/guardrails/utils.py +305 -0
  22. rasa/builder/job_manager.py +87 -0
  23. rasa/builder/jobs.py +232 -0
  24. rasa/builder/llm_service.py +59 -7
  25. rasa/builder/logging_utils.py +162 -4
  26. rasa/builder/main.py +29 -16
  27. rasa/builder/models.py +90 -233
  28. rasa/builder/project_generator.py +91 -7
  29. rasa/builder/scrape_rasa_docs.py +1 -1
  30. rasa/builder/service.py +632 -440
  31. rasa/builder/shared/tracker_context.py +212 -0
  32. rasa/builder/validation_service.py +4 -4
  33. rasa/cli/data.py +8 -3
  34. rasa/cli/project_templates/basic/actions/action_api.py +15 -0
  35. rasa/cli/project_templates/basic/actions/action_human_handoff.py +44 -0
  36. rasa/cli/project_templates/basic/config.yml +23 -0
  37. rasa/cli/project_templates/{plain → basic}/credentials.yml +8 -7
  38. rasa/cli/project_templates/basic/data/general/feedback.yml +20 -0
  39. rasa/cli/project_templates/basic/data/general/goodbye.yml +6 -0
  40. rasa/cli/project_templates/basic/data/general/hello.yml +7 -0
  41. rasa/cli/project_templates/basic/data/general/help.yml +6 -0
  42. rasa/cli/project_templates/basic/data/general/human_handoff.yml +16 -0
  43. rasa/cli/project_templates/basic/data/general/welcome.yml +9 -0
  44. rasa/cli/project_templates/{finance/data/patterns → basic/data/system}/pattern_completed.yml +2 -1
  45. rasa/cli/project_templates/basic/data/system/pattern_correction.yml +7 -0
  46. rasa/cli/project_templates/basic/data/system/pattern_search.yml +8 -0
  47. rasa/cli/project_templates/basic/data/system/pattern_session_start.yml +8 -0
  48. rasa/cli/project_templates/basic/docs/rasa_assistant_qa.txt +65 -0
  49. rasa/cli/project_templates/basic/docs/template.txt +7 -0
  50. rasa/cli/project_templates/basic/domain/general/assistant_details.yml +12 -0
  51. rasa/cli/project_templates/basic/domain/general/bot_identity.yml +5 -0
  52. rasa/cli/project_templates/basic/domain/general/cannot_handle.yml +5 -0
  53. rasa/cli/project_templates/basic/domain/general/feedback.yml +28 -0
  54. rasa/cli/project_templates/basic/domain/general/goodbye.yml +7 -0
  55. rasa/cli/project_templates/basic/domain/general/help.yml +5 -0
  56. rasa/cli/project_templates/basic/domain/general/human_handoff_domain.yml +35 -0
  57. rasa/cli/project_templates/{finance/domain/default_actions.yml → basic/domain/general/utils.yml} +0 -3
  58. rasa/cli/project_templates/basic/domain/general/welcome.yml +7 -0
  59. rasa/cli/project_templates/{plain → basic}/endpoints.yml +42 -27
  60. rasa/cli/project_templates/basic/prompts/rephraser_demo_personality_prompt.jinja2 +19 -0
  61. rasa/cli/project_templates/defaults.py +25 -3
  62. rasa/cli/project_templates/finance/actions/__init__.py +46 -0
  63. rasa/cli/project_templates/finance/actions/accounts/__init__.py +0 -0
  64. rasa/cli/project_templates/finance/actions/{action_ask_account.py → accounts/action_ask_account.py} +6 -9
  65. rasa/cli/project_templates/finance/actions/{action_check_balance.py → accounts/action_check_balance.py} +4 -4
  66. rasa/cli/project_templates/finance/actions/action_session_start.py +11 -6
  67. rasa/cli/project_templates/finance/actions/cards/__init__.py +0 -0
  68. rasa/cli/project_templates/finance/actions/{action_ask_card.py → cards/action_ask_card.py} +4 -3
  69. rasa/cli/project_templates/finance/actions/{action_check_card_existence.py → cards/action_check_card_existence.py} +4 -3
  70. rasa/cli/project_templates/finance/actions/{action_update_card_status.py → cards/action_update_card_status.py} +18 -9
  71. rasa/cli/project_templates/finance/actions/database.py +1 -0
  72. rasa/cli/project_templates/finance/actions/transfers/__init__.py +0 -0
  73. rasa/cli/project_templates/finance/actions/{action_add_payee.py → transfers/action_add_payee.py} +8 -3
  74. rasa/cli/project_templates/finance/actions/{action_ask_account_from.py → transfers/action_ask_account_from.py} +5 -4
  75. rasa/cli/project_templates/finance/actions/{action_check_payee_existence.py → transfers/action_check_payee_existence.py} +3 -3
  76. rasa/cli/project_templates/finance/actions/{action_check_sufficient_funds.py → transfers/action_check_sufficient_funds.py} +3 -4
  77. rasa/cli/project_templates/finance/actions/{action_list_payees.py → transfers/action_list_payees.py} +4 -3
  78. rasa/cli/project_templates/finance/actions/{action_remove_payee.py → transfers/action_remove_payee.py} +4 -4
  79. rasa/cli/project_templates/finance/config.yml +8 -19
  80. rasa/cli/project_templates/finance/credentials.yml +6 -7
  81. rasa/cli/project_templates/finance/csvs/cards.csv +10 -10
  82. rasa/cli/project_templates/finance/csvs/payees.csv +10 -9
  83. rasa/cli/project_templates/finance/data/{flows → accounts}/check_balance.yml +2 -1
  84. rasa/cli/project_templates/finance/data/general/bot_identity.yml +6 -0
  85. rasa/cli/project_templates/finance/data/general/feedback.yml +20 -0
  86. rasa/cli/project_templates/finance/data/general/goodbye.yml +6 -0
  87. rasa/cli/project_templates/finance/data/general/hello.yml +7 -0
  88. rasa/cli/project_templates/finance/data/{flows/welcome.yml → general/help.yml} +2 -7
  89. rasa/cli/project_templates/finance/data/general/human_handoff.yml +16 -0
  90. rasa/cli/project_templates/finance/data/general/welcome.yml +9 -0
  91. rasa/cli/project_templates/finance/data/{patterns → system/patterns}/pattern_chitchat.yml +0 -2
  92. rasa/cli/project_templates/finance/data/system/patterns/pattern_completed.yml +7 -0
  93. rasa/cli/project_templates/finance/data/system/patterns/pattern_correction.yml +7 -0
  94. rasa/cli/project_templates/finance/data/system/patterns/pattern_search.yml +8 -0
  95. rasa/cli/project_templates/finance/data/{patterns → system/patterns}/pattern_session_start.yml +0 -1
  96. rasa/cli/project_templates/finance/domain/{check_balance.yml → accounts/check_balance.yml} +2 -0
  97. rasa/cli/project_templates/finance/domain/general/assistant_details.yml +12 -0
  98. rasa/cli/project_templates/finance/domain/general/bot_identity.yml +5 -0
  99. rasa/cli/project_templates/finance/domain/general/cannot_handle.yml +5 -0
  100. rasa/cli/project_templates/finance/domain/general/defaults.yml +24 -0
  101. rasa/cli/project_templates/finance/domain/general/feedback.yml +28 -0
  102. rasa/cli/project_templates/finance/domain/general/goodbye.yml +7 -0
  103. rasa/cli/project_templates/finance/domain/general/help.yml +5 -0
  104. rasa/cli/project_templates/finance/domain/general/human_handoff.yml +30 -0
  105. rasa/cli/project_templates/finance/domain/general/utils.yml +13 -0
  106. rasa/cli/project_templates/finance/domain/general/welcome.yml +8 -0
  107. rasa/cli/project_templates/finance/endpoints.yml +1 -0
  108. rasa/cli/project_templates/finance/prompts/rephraser_demo_personality_prompt.jinja2 +3 -3
  109. rasa/cli/project_templates/telco/actions/actions_billing.py +24 -17
  110. rasa/cli/project_templates/telco/actions/actions_get_data_from_db.py +6 -1
  111. rasa/cli/project_templates/telco/actions/actions_run_diagnostics.py +6 -1
  112. rasa/cli/project_templates/telco/actions/actions_session_start.py +6 -1
  113. rasa/cli/project_templates/tutorial/config.yml +2 -1
  114. rasa/cli/scaffold.py +27 -2
  115. rasa/cli/train.py +8 -0
  116. rasa/cli/utils.py +31 -15
  117. rasa/core/actions/action.py +28 -41
  118. rasa/core/actions/action_run_slot_rejections.py +1 -1
  119. rasa/core/channels/development_inspector.py +47 -14
  120. rasa/core/channels/inspector/dist/assets/{arc-371401b1.js → arc-1ddec37b.js} +1 -1
  121. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-3f126156.js → blockDiagram-38ab4fdb-18af387c.js} +1 -1
  122. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-12f22eb7.js → c4Diagram-3d4e48cf-250127a3.js} +1 -1
  123. rasa/core/channels/inspector/dist/assets/channel-59f6d54b.js +1 -0
  124. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-03b1d386.js → classDiagram-70f12bd4-c3388b34.js} +1 -1
  125. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-84f69d63.js → classDiagram-v2-f2320105-9c893a82.js} +1 -1
  126. rasa/core/channels/inspector/dist/assets/clone-26177ddb.js +1 -0
  127. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-ca47fd38.js → createText-2e5e7dd3-c111213b.js} +1 -1
  128. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-f837ca8a.js → edges-e0da2a9e-812a729d.js} +1 -1
  129. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-8717ac54.js → erDiagram-9861fffd-fd5051bc.js} +1 -1
  130. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-94f38b83.js → flowDb-956e92f1-3287ac02.js} +1 -1
  131. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-b616f9fb.js → flowDiagram-66a62f08-692fb0b2.js} +1 -1
  132. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-29c03f5a.js +1 -0
  133. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-f5d24bb8.js → flowchart-elk-definition-4a651766-008376f1.js} +1 -1
  134. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-b43ba8d9.js → ganttDiagram-c361ad54-df330a69.js} +1 -1
  135. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-c3aafaa5.js → gitGraphDiagram-72cf32ee-e03676fb.js} +1 -1
  136. rasa/core/channels/inspector/dist/assets/{graph-0d0a2c10.js → graph-46fad2ba.js} +1 -1
  137. rasa/core/channels/inspector/dist/assets/{index-3862675e-58ea0305.js → index-3862675e-a484ac55.js} +1 -1
  138. rasa/core/channels/inspector/dist/assets/{index-cce6f8a1.js → index-a003633f.js} +179 -179
  139. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b8f60461.js → infoDiagram-f8f76790-3f9e6ec2.js} +1 -1
  140. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-95be5545.js → journeyDiagram-49397b02-79f72383.js} +1 -1
  141. rasa/core/channels/inspector/dist/assets/{layout-da885b9b.js → layout-aad098e5.js} +1 -1
  142. rasa/core/channels/inspector/dist/assets/{line-f1c817d3.js → line-219ab7ae.js} +1 -1
  143. rasa/core/channels/inspector/dist/assets/{linear-d42801e6.js → linear-2cddbe62.js} +1 -1
  144. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-a38923a6.js → mindmap-definition-fc14e90a-1d41ed99.js} +1 -1
  145. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-ca6e71e9.js → pieDiagram-8a3498a8-cc496ee8.js} +1 -1
  146. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b290dae9.js → quadrantDiagram-120e2f19-84d32884.js} +1 -1
  147. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-03f02ceb.js → requirementDiagram-deff3bca-c0deb984.js} +1 -1
  148. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c49eee40.js → sankeyDiagram-04a897e0-b9d7fd62.js} +1 -1
  149. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-b2cd6a3d.js → sequenceDiagram-704730f1-7d517565.js} +1 -1
  150. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-e53a2028.js → stateDiagram-587899a1-98ef9b27.js} +1 -1
  151. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e1982a03.js → stateDiagram-v2-d93cdb3a-cee70748.js} +1 -1
  152. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-d0226ca5.js → styles-6aaf32cf-3f9d1c96.js} +1 -1
  153. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-0e21dc00.js → styles-9a916d00-67471923.js} +1 -1
  154. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-9588494e.js → styles-c10674c1-bd093fb7.js} +1 -1
  155. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-be478d4f.js → svgDrawCommon-08f97a94-675794e8.js} +1 -1
  156. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-74631749.js → timeline-definition-85554ec2-0ac67617.js} +1 -1
  157. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-a043552f.js → xychartDiagram-e933f94c-c018dc37.js} +1 -1
  158. rasa/core/channels/inspector/dist/index.html +2 -2
  159. rasa/core/channels/inspector/index.html +1 -1
  160. rasa/core/channels/inspector/package.json +4 -3
  161. rasa/core/channels/inspector/src/App.tsx +53 -7
  162. rasa/core/channels/inspector/src/components/Chat.tsx +3 -2
  163. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +1 -1
  164. rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +268 -0
  165. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -2
  166. rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +8 -3
  167. rasa/core/channels/inspector/src/types.ts +8 -0
  168. rasa/core/channels/inspector/yarn.lock +12 -12
  169. rasa/core/channels/studio_chat.py +119 -34
  170. rasa/core/channels/voice_ready/twilio_voice.py +1 -1
  171. rasa/core/channels/voice_stream/asr/asr_engine.py +5 -1
  172. rasa/core/channels/voice_stream/asr/deepgram.py +5 -0
  173. rasa/core/channels/voice_stream/audiocodes.py +16 -8
  174. rasa/core/channels/voice_stream/browser_audio.py +39 -4
  175. rasa/core/channels/voice_stream/call_state.py +13 -2
  176. rasa/core/channels/voice_stream/genesys.py +16 -13
  177. rasa/core/channels/voice_stream/jambonz.py +14 -12
  178. rasa/core/channels/voice_stream/twilio_media_streams.py +14 -13
  179. rasa/core/channels/voice_stream/util.py +11 -1
  180. rasa/core/channels/voice_stream/voice_channel.py +108 -29
  181. rasa/core/nlg/callback.py +1 -1
  182. rasa/core/nlg/contextual_response_rephraser.py +19 -9
  183. rasa/core/nlg/generator.py +21 -5
  184. rasa/core/nlg/response.py +43 -6
  185. rasa/core/nlg/translate.py +8 -0
  186. rasa/core/policies/enterprise_search_policy.py +16 -21
  187. rasa/dialogue_understanding/commands/correct_slots_command.py +38 -10
  188. rasa/dialogue_understanding/generator/command_generator.py +5 -5
  189. rasa/dialogue_understanding/generator/command_parser.py +9 -13
  190. rasa/dialogue_understanding/processor/command_processor.py +149 -55
  191. rasa/dialogue_understanding/stack/utils.py +13 -3
  192. rasa/dialogue_understanding_test/du_test_schema.yml +3 -3
  193. rasa/dialogue_understanding_test/validation.py +9 -10
  194. rasa/e2e_test/e2e_config.py +18 -11
  195. rasa/e2e_test/e2e_test_schema.yml +3 -3
  196. rasa/e2e_test/utils/validation.py +17 -19
  197. rasa/engine/validation.py +86 -91
  198. rasa/exceptions.py +26 -1
  199. rasa/model_manager/model_api.py +2 -2
  200. rasa/model_manager/socket_bridge.py +8 -2
  201. rasa/shared/providers/_configs/default_litellm_client_config.py +3 -7
  202. rasa/shared/utils/cli.py +2 -0
  203. rasa/shared/utils/common.py +2 -1
  204. rasa/shared/utils/health_check/health_check.py +10 -14
  205. rasa/studio/upload.py +6 -2
  206. rasa/studio/utils.py +33 -22
  207. rasa/telemetry.py +95 -22
  208. rasa/utils/licensing.py +21 -10
  209. rasa/utils/log_utils.py +1 -1
  210. rasa/utils/tensorflow/transformer.py +3 -3
  211. rasa/validator.py +7 -5
  212. rasa/version.py +1 -1
  213. {rasa_pro-3.13.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/METADATA +7 -7
  214. {rasa_pro-3.13.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/RECORD +242 -205
  215. rasa/builder/constants.py +0 -4
  216. rasa/builder/copilot-llm-structured-output-response-schema.json +0 -69
  217. rasa/builder/copilot.py +0 -233
  218. rasa/builder/copilot_system_prompt.jinja2 +0 -245
  219. rasa/builder/create_openai_vector_store.py +0 -228
  220. rasa/builder/llm_context.py +0 -81
  221. rasa/cli/project_templates/finance/data/nlu.yml +0 -29
  222. rasa/cli/project_templates/finance/data/patterns/pattern_search.yml +0 -5
  223. rasa/cli/project_templates/finance/domain/default_flows.yml +0 -33
  224. rasa/cli/project_templates/finance/prompts/command-generator.jinja2 +0 -57
  225. rasa/cli/project_templates/finance/tests/conversation_repair/cancellations.yml +0 -12
  226. rasa/cli/project_templates/finance/tests/conversation_repair/cannot_handle.yml +0 -7
  227. rasa/cli/project_templates/finance/tests/conversation_repair/chitchat.yml +0 -7
  228. rasa/cli/project_templates/finance/tests/conversation_repair/clarification.yml +0 -9
  229. rasa/cli/project_templates/finance/tests/conversation_repair/completion.yml +0 -18
  230. rasa/cli/project_templates/finance/tests/conversation_repair/corrections.yml +0 -17
  231. rasa/cli/project_templates/finance/tests/conversation_repair/digressions.yml +0 -32
  232. rasa/cli/project_templates/finance/tests/conversation_repair/human_handoff.yml +0 -21
  233. rasa/cli/project_templates/finance/tests/conversation_repair/skipping_collect_steps.yml +0 -16
  234. rasa/cli/project_templates/finance/tests/demo_scripts/main.yml +0 -16
  235. rasa/cli/project_templates/finance/tests/happy_paths/balance_verification.yml +0 -15
  236. rasa/cli/project_templates/finance/tests/happy_paths/banking_questions.yml +0 -12
  237. rasa/cli/project_templates/finance/tests/happy_paths/card_blocking.yml +0 -52
  238. rasa/cli/project_templates/finance/tests/happy_paths/money_transfer.yml +0 -136
  239. rasa/cli/project_templates/finance/tests/happy_paths/payee_management.yml +0 -27
  240. rasa/cli/project_templates/finance/tests/happy_paths/user_greeted.yml +0 -5
  241. rasa/cli/project_templates/plain/config.yml +0 -17
  242. rasa/cli/project_templates/plain/data/patterns/pattern_session_start.yml +0 -7
  243. rasa/cli/project_templates/plain/domain.yml +0 -5
  244. rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +0 -1
  245. rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +0 -1
  246. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +0 -1
  247. rasa/shared/importers/static.py +0 -63
  248. /rasa/{cli/project_templates/plain/actions → builder/copilot}/__init__.py +0 -0
  249. /rasa/builder/{inkeep-rag-response-schema.json → document_retrieval/inkeep-rag-response-schema.json} +0 -0
  250. /rasa/cli/project_templates/finance/actions/{action_process_immediate_payment.py → transfers/action_process_immediate_payment.py} +0 -0
  251. /rasa/cli/project_templates/finance/actions/{action_schedule_payment.py → transfers/action_schedule_payment.py} +0 -0
  252. /rasa/cli/project_templates/finance/actions/{action_validate_payment_date.py → transfers/action_validate_payment_date.py} +0 -0
  253. /rasa/cli/project_templates/finance/data/{flows → cards}/block_card.yml +0 -0
  254. /rasa/cli/project_templates/finance/data/{flows → cards}/select_card.yml +0 -0
  255. /rasa/cli/project_templates/finance/data/{source → system/source}/accounts.json +0 -0
  256. /rasa/cli/project_templates/finance/data/{source → system/source}/advisors.json +0 -0
  257. /rasa/cli/project_templates/finance/data/{source → system/source}/appointments.json +0 -0
  258. /rasa/cli/project_templates/finance/data/{source → system/source}/branches.json +0 -0
  259. /rasa/cli/project_templates/finance/data/{source → system/source}/cards.json +0 -0
  260. /rasa/cli/project_templates/finance/data/{source → system/source}/payees.json +0 -0
  261. /rasa/cli/project_templates/finance/data/{source → system/source}/transactions.json +0 -0
  262. /rasa/cli/project_templates/finance/data/{source → system/source}/users.json +0 -0
  263. /rasa/cli/project_templates/finance/data/{flows → transfers}/add_payee.yml +0 -0
  264. /rasa/cli/project_templates/finance/data/{flows → transfers}/list_payees.yml +0 -0
  265. /rasa/cli/project_templates/finance/data/{flows → transfers}/remove_payee.yml +0 -0
  266. /rasa/cli/project_templates/finance/data/{flows → transfers}/transfer_money.yml +0 -0
  267. /rasa/cli/project_templates/finance/domain/{block_card.yml → cards/block_card.yml} +0 -0
  268. /rasa/cli/project_templates/finance/domain/{select_card.yml → cards/select_card.yml} +0 -0
  269. /rasa/cli/project_templates/finance/domain/{add_payee.yml → transfers/add_payee.yml} +0 -0
  270. /rasa/cli/project_templates/finance/domain/{list_payees.yml → transfers/list_payees.yml} +0 -0
  271. /rasa/cli/project_templates/finance/domain/{remove_payee.yml → transfers/remove_payee.yml} +0 -0
  272. /rasa/cli/project_templates/finance/domain/{transfer_money.yml → transfers/transfer_money.yml} +0 -0
  273. {rasa_pro-3.13.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/NOTICE +0 -0
  274. {rasa_pro-3.13.1a19.dist-info → rasa_pro-3.13.1a20.dist-info}/WHEEL +0 -0
  275. {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
- """Main service for the prompt-to-bot functionality."""
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.exceptions import (
13
- LLMGenerationError,
14
- ProjectGenerationError,
15
- TrainingError,
16
- ValidationError,
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 get_recent_logs
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
- CopilotChatMessage,
23
- CopilotContext,
24
- CopilotRequest,
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.training_service import train_and_load_agent
31
- from rasa.builder.validation_service import validate_project
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.studio.upload import CALMUserData, extract_calm_import_parts_from_importer
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 extract_calm_import_parts_from_project_generator(
77
- project_generator: ProjectGenerator,
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
- project_generator: The project generator to extract data from
113
+ agent: The agent to extract data from
83
114
 
84
115
  Returns:
85
- CALMUserData containing flows, domain, config, endpoints, and nlu data
116
+ BotData containing flows, domain, config, endpoints, and nlu data
86
117
  """
87
- # Get the training data importer
88
- importer = project_generator._create_importer()
89
-
90
- # Extract endpoints (if exists)
91
- endpoints_path = project_generator.project_folder / "endpoints.yml"
92
- if endpoints_path.exists():
93
- from rasa.shared.utils.yaml import read_yaml_file
94
-
95
- endpoints = read_yaml_file(endpoints_path, expand_env_vars=False)
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
- "using LLM generation. Returns server-sent events (SSE) for real-time "
123
- "progress tracking through the entire bot creation process.\n\n"
124
- "**SSE Event Flow:**\n"
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 (can occur at any time):**\n"
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:** Send POST request with Content-Type: application/json and "
137
- "Accept: text/event-stream"
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
- {"text/event-stream": str},
148
- description="Server-sent events stream with real-time progress updates",
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) -> None:
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
- # 1. Received
168
- await _send_sse_event(
169
- sse_response,
170
- ServerSentEvent(event="received", data={"status": "received"}),
171
- )
172
-
173
- # Validate request
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
- except Exception as e:
269
- structlogger.error(
270
- "bot_builder_service.prompt_to_bot.unexpected_error", error=str(e)
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
- await _send_sse_event(
273
- sse_response,
274
- ServerSentEvent(event="error", data={"status": "error", "error": str(e)}),
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 with "
284
- "immediate setup. Returns server-sent events (SSE) for real-time progress "
285
- "tracking through the entire bot creation process.\n\n"
286
- "**SSE Event Flow:**\n"
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 (can occur at any time):**\n"
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:** Send POST request with Content-Type: application/json and "
299
- "Accept: text/event-stream\n"
300
- "**Templates Available:** Check available templates through the API or "
301
- "documentation"
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
- {"text/event-stream": model_to_schema(ServerSentEvent)},
312
- description="Server-sent events stream with real-time progress updates",
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) -> None:
329
- """Handle template-to-bot generation requests."""
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
- # 2. Generating
345
- await _send_sse_event(
346
- sse_response,
347
- ServerSentEvent(event="generating", data={"status": "generating"}),
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
- except ValidationError as e:
425
- structlogger.error(
426
- "bot_builder_service.template_to_bot.validation_error", error=str(e)
427
- )
428
- await _send_sse_event(
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 e:
437
- structlogger.error(
438
- "bot_builder_service.template_to_bot.unexpected_error", error=str(e)
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
- await _send_sse_event(
441
- sse_response,
442
- ServerSentEvent(event="error", data={"status": "error", "error": str(e)}),
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
- project_generator = get_project_generator(request)
465
- bot_files = project_generator.get_bot_files()
466
- return response.json(bot_files)
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 server-sent events (SSE) for real-time progress tracking "
474
- "through the entire update process.\n\n"
475
- "**SSE Event Flow:**\n"
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:** Send PUT request with Content-Type: application/json and "
487
- "Accept: text/event-stream"
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
- {"text/event-stream": str},
500
- description="Server-sent events stream with update progress",
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) -> None:
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
- project_generator.update_bot_files(bot_files)
528
-
529
- # 2. Validating
530
- await _send_sse_event(
531
- sse_response,
532
- ServerSentEvent(event="validating", data={"status": "validating"}),
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
- try:
568
- request.app.ctx.agent = await train_and_load_agent(importer)
569
- input_channel.agent = request.app.ctx.agent
570
-
571
- await _send_sse_event(
572
- sse_response,
573
- ServerSentEvent(
574
- event="train_success", data={"status": "train_success"}
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
- except Exception as e:
601
- await _send_sse_event(
602
- sse_response,
603
- ServerSentEvent(event="error", data={"status": "error", "error": str(e)}),
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(CALMUserData)},
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
- project_generator = get_project_generator(request)
630
- calm_parts = extract_calm_import_parts_from_project_generator(project_generator)
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
- return response.json(calm_parts.model_dump())
633
- except Exception as e:
634
- structlogger.error("bot_builder_service.get_bot_data.error", error=str(e))
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 for conversational bot development, "
648
- "including debugging, suggestions, and explanations."
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, and"
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
- {"application/json": model_to_schema(CopilotChatMessage)},
664
- description="Copilot response with assistance and suggestions.",
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) -> HTTPResponse:
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 request
688
- copilot_request = CopilotRequest(**request.json)
815
+ # 1. Validate and unpack input
816
+ req = CopilotRequest(**request.json)
689
817
 
690
- # Get current conversation context
691
- current_tracker = await current_tracker_from_input_channel(
692
- request.app, input_channel
818
+ telemetry = CopilotTelemetry(
819
+ project_id=HELLO_RASA_PROJECT_ID,
820
+ user_id=request.headers.get(HEADER_USER_ID),
693
821
  )
694
- assistant_logs = get_recent_logs()
695
- assistant_files = project_generator.get_bot_files()
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
- # Create LLM builder context
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
- tracker=current_tracker,
700
- assistant_logs=assistant_logs,
701
- assistant_files=assistant_files,
702
- copilot_chat_history=copilot_request.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
- # Generate response
706
- copilot_response = await llm_service.copilot.generate_response(context)
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
- return response.json(copilot_response.model_dump())
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
- except LLMGenerationError as e:
711
- structlogger.error(
712
- "bot_builder_service.copilot_helper.generation_error", error=str(e)
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
- return response.json(
715
- ApiErrorResponse(
716
- error="LLM helper generation failed", details={"llm_error": str(e)}
717
- ).model_dump(),
718
- status=502,
908
+ await sse.send(
909
+ ServerSentEvent(
910
+ event="error",
911
+ data={"error": str(e)},
912
+ ).format()
719
913
  )
720
914
 
721
- except Exception as e:
722
- structlogger.error(
723
- "bot_builder_service.copilot_helper.unexpected_error", error=str(e)
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
- return response.json(
726
- ApiErrorResponse(
727
- error="Unexpected error in LLM builder",
728
- details=None,
729
- ).model_dump(),
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, input_channel: StudioChatInput
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 input_channel.latest_tracker_session_id:
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())