rasa-pro 3.13.1a18__py3-none-any.whl → 3.13.1a20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- rasa/__main__.py +8 -0
- rasa/builder/auth.py +71 -0
- rasa/builder/config.py +16 -0
- rasa/builder/copilot/constants.py +15 -0
- rasa/builder/copilot/copilot.py +342 -0
- rasa/builder/copilot/copilot_response_handler.py +471 -0
- rasa/builder/copilot/exceptions.py +20 -0
- rasa/builder/copilot/models.py +344 -0
- rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +495 -0
- rasa/builder/copilot/telemetry.py +195 -0
- rasa/builder/document_retrieval/__init__.py +0 -0
- rasa/builder/document_retrieval/constants.py +16 -0
- rasa/builder/{inkeep_document_retrieval.py → document_retrieval/inkeep_document_retrieval.py} +53 -44
- rasa/builder/document_retrieval/models.py +62 -0
- rasa/builder/download.py +140 -0
- rasa/builder/guardrails/__init__.py +1 -0
- rasa/builder/guardrails/constants.py +4 -0
- rasa/builder/guardrails/exceptions.py +4 -0
- rasa/builder/guardrails/lakera.py +188 -0
- rasa/builder/guardrails/models.py +199 -0
- rasa/builder/guardrails/utils.py +305 -0
- rasa/builder/job_manager.py +87 -0
- rasa/builder/jobs.py +232 -0
- rasa/builder/llm_service.py +89 -173
- rasa/builder/logging_utils.py +162 -4
- rasa/builder/main.py +29 -16
- rasa/builder/models.py +93 -121
- rasa/builder/project_generator.py +91 -7
- rasa/builder/scrape_rasa_docs.py +1 -1
- rasa/builder/service.py +650 -452
- rasa/builder/shared/tracker_context.py +212 -0
- rasa/builder/validation_service.py +4 -4
- rasa/cli/data.py +8 -3
- rasa/cli/project_templates/basic/actions/action_api.py +15 -0
- rasa/cli/project_templates/basic/actions/action_human_handoff.py +44 -0
- rasa/cli/project_templates/basic/config.yml +23 -0
- rasa/cli/project_templates/{plain → basic}/credentials.yml +8 -7
- rasa/cli/project_templates/basic/data/general/feedback.yml +20 -0
- rasa/cli/project_templates/basic/data/general/goodbye.yml +6 -0
- rasa/cli/project_templates/basic/data/general/hello.yml +7 -0
- rasa/cli/project_templates/basic/data/general/help.yml +6 -0
- rasa/cli/project_templates/basic/data/general/human_handoff.yml +16 -0
- rasa/cli/project_templates/basic/data/general/welcome.yml +9 -0
- rasa/cli/project_templates/{finance/data/patterns → basic/data/system}/pattern_completed.yml +2 -1
- rasa/cli/project_templates/basic/data/system/pattern_correction.yml +7 -0
- rasa/cli/project_templates/basic/data/system/pattern_search.yml +8 -0
- rasa/cli/project_templates/basic/data/system/pattern_session_start.yml +8 -0
- rasa/cli/project_templates/basic/docs/rasa_assistant_qa.txt +65 -0
- rasa/cli/project_templates/basic/docs/template.txt +7 -0
- rasa/cli/project_templates/basic/domain/general/assistant_details.yml +12 -0
- rasa/cli/project_templates/basic/domain/general/bot_identity.yml +5 -0
- rasa/cli/project_templates/basic/domain/general/cannot_handle.yml +5 -0
- rasa/cli/project_templates/basic/domain/general/feedback.yml +28 -0
- rasa/cli/project_templates/basic/domain/general/goodbye.yml +7 -0
- rasa/cli/project_templates/basic/domain/general/help.yml +5 -0
- rasa/cli/project_templates/basic/domain/general/human_handoff_domain.yml +35 -0
- rasa/cli/project_templates/{finance/domain/default_actions.yml → basic/domain/general/utils.yml} +0 -3
- rasa/cli/project_templates/basic/domain/general/welcome.yml +7 -0
- rasa/cli/project_templates/{plain → basic}/endpoints.yml +42 -27
- rasa/cli/project_templates/basic/prompts/rephraser_demo_personality_prompt.jinja2 +19 -0
- rasa/cli/project_templates/defaults.py +25 -3
- rasa/cli/project_templates/finance/actions/__init__.py +46 -0
- rasa/cli/project_templates/finance/actions/accounts/__init__.py +0 -0
- rasa/cli/project_templates/finance/actions/{action_ask_account.py → accounts/action_ask_account.py} +6 -9
- rasa/cli/project_templates/finance/actions/{action_check_balance.py → accounts/action_check_balance.py} +4 -4
- rasa/cli/project_templates/finance/actions/action_session_start.py +11 -6
- rasa/cli/project_templates/finance/actions/cards/__init__.py +0 -0
- rasa/cli/project_templates/finance/actions/{action_ask_card.py → cards/action_ask_card.py} +4 -3
- rasa/cli/project_templates/finance/actions/{action_check_card_existence.py → cards/action_check_card_existence.py} +4 -3
- rasa/cli/project_templates/finance/actions/{action_update_card_status.py → cards/action_update_card_status.py} +18 -9
- rasa/cli/project_templates/finance/actions/database.py +1 -0
- rasa/cli/project_templates/finance/actions/transfers/__init__.py +0 -0
- rasa/cli/project_templates/finance/actions/{action_add_payee.py → transfers/action_add_payee.py} +8 -3
- rasa/cli/project_templates/finance/actions/{action_ask_account_from.py → transfers/action_ask_account_from.py} +5 -4
- rasa/cli/project_templates/finance/actions/{action_check_payee_existence.py → transfers/action_check_payee_existence.py} +3 -3
- rasa/cli/project_templates/finance/actions/{action_check_sufficient_funds.py → transfers/action_check_sufficient_funds.py} +3 -4
- rasa/cli/project_templates/finance/actions/{action_list_payees.py → transfers/action_list_payees.py} +4 -3
- rasa/cli/project_templates/finance/actions/{action_remove_payee.py → transfers/action_remove_payee.py} +4 -4
- rasa/cli/project_templates/finance/config.yml +8 -19
- rasa/cli/project_templates/finance/credentials.yml +6 -7
- rasa/cli/project_templates/finance/csvs/cards.csv +10 -10
- rasa/cli/project_templates/finance/csvs/payees.csv +10 -9
- rasa/cli/project_templates/finance/data/{flows → accounts}/check_balance.yml +2 -1
- rasa/cli/project_templates/finance/data/general/bot_identity.yml +6 -0
- rasa/cli/project_templates/finance/data/general/feedback.yml +20 -0
- rasa/cli/project_templates/finance/data/general/goodbye.yml +6 -0
- rasa/cli/project_templates/finance/data/general/hello.yml +7 -0
- rasa/cli/project_templates/finance/data/{flows/welcome.yml → general/help.yml} +2 -7
- rasa/cli/project_templates/finance/data/general/human_handoff.yml +16 -0
- rasa/cli/project_templates/finance/data/general/welcome.yml +9 -0
- rasa/cli/project_templates/finance/data/{patterns → system/patterns}/pattern_chitchat.yml +0 -2
- rasa/cli/project_templates/finance/data/system/patterns/pattern_completed.yml +7 -0
- rasa/cli/project_templates/finance/data/system/patterns/pattern_correction.yml +7 -0
- rasa/cli/project_templates/finance/data/system/patterns/pattern_search.yml +8 -0
- rasa/cli/project_templates/finance/data/{patterns → system/patterns}/pattern_session_start.yml +0 -1
- rasa/cli/project_templates/finance/domain/{check_balance.yml → accounts/check_balance.yml} +2 -0
- rasa/cli/project_templates/finance/domain/general/assistant_details.yml +12 -0
- rasa/cli/project_templates/finance/domain/general/bot_identity.yml +5 -0
- rasa/cli/project_templates/finance/domain/general/cannot_handle.yml +5 -0
- rasa/cli/project_templates/finance/domain/general/defaults.yml +24 -0
- rasa/cli/project_templates/finance/domain/general/feedback.yml +28 -0
- rasa/cli/project_templates/finance/domain/general/goodbye.yml +7 -0
- rasa/cli/project_templates/finance/domain/general/help.yml +5 -0
- rasa/cli/project_templates/finance/domain/general/human_handoff.yml +30 -0
- rasa/cli/project_templates/finance/domain/general/utils.yml +13 -0
- rasa/cli/project_templates/finance/domain/general/welcome.yml +8 -0
- rasa/cli/project_templates/finance/endpoints.yml +1 -0
- rasa/cli/project_templates/finance/prompts/rephraser_demo_personality_prompt.jinja2 +3 -3
- rasa/cli/project_templates/telco/actions/actions_billing.py +24 -17
- rasa/cli/project_templates/telco/actions/actions_get_data_from_db.py +6 -1
- rasa/cli/project_templates/telco/actions/actions_run_diagnostics.py +6 -1
- rasa/cli/project_templates/telco/actions/actions_session_start.py +6 -1
- rasa/cli/project_templates/tutorial/config.yml +2 -1
- rasa/cli/scaffold.py +27 -2
- rasa/cli/train.py +8 -0
- rasa/cli/utils.py +31 -15
- rasa/core/actions/action.py +28 -41
- rasa/core/actions/action_run_slot_rejections.py +1 -1
- rasa/core/channels/development_inspector.py +47 -14
- rasa/core/channels/inspector/dist/assets/{arc-371401b1.js → arc-1ddec37b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-3f126156.js → blockDiagram-38ab4fdb-18af387c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-12f22eb7.js → c4Diagram-3d4e48cf-250127a3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-59f6d54b.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-03b1d386.js → classDiagram-70f12bd4-c3388b34.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-84f69d63.js → classDiagram-v2-f2320105-9c893a82.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-26177ddb.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-ca47fd38.js → createText-2e5e7dd3-c111213b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-f837ca8a.js → edges-e0da2a9e-812a729d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-8717ac54.js → erDiagram-9861fffd-fd5051bc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-94f38b83.js → flowDb-956e92f1-3287ac02.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-b616f9fb.js → flowDiagram-66a62f08-692fb0b2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-29c03f5a.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-f5d24bb8.js → flowchart-elk-definition-4a651766-008376f1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-b43ba8d9.js → ganttDiagram-c361ad54-df330a69.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-c3aafaa5.js → gitGraphDiagram-72cf32ee-e03676fb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-0d0a2c10.js → graph-46fad2ba.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-58ea0305.js → index-3862675e-a484ac55.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-cce6f8a1.js → index-a003633f.js} +179 -179
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b8f60461.js → infoDiagram-f8f76790-3f9e6ec2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-95be5545.js → journeyDiagram-49397b02-79f72383.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-da885b9b.js → layout-aad098e5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-f1c817d3.js → line-219ab7ae.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-d42801e6.js → linear-2cddbe62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-a38923a6.js → mindmap-definition-fc14e90a-1d41ed99.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-ca6e71e9.js → pieDiagram-8a3498a8-cc496ee8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b290dae9.js → quadrantDiagram-120e2f19-84d32884.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-03f02ceb.js → requirementDiagram-deff3bca-c0deb984.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c49eee40.js → sankeyDiagram-04a897e0-b9d7fd62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-b2cd6a3d.js → sequenceDiagram-704730f1-7d517565.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-e53a2028.js → stateDiagram-587899a1-98ef9b27.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e1982a03.js → stateDiagram-v2-d93cdb3a-cee70748.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-d0226ca5.js → styles-6aaf32cf-3f9d1c96.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-0e21dc00.js → styles-9a916d00-67471923.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-9588494e.js → styles-c10674c1-bd093fb7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-be478d4f.js → svgDrawCommon-08f97a94-675794e8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-74631749.js → timeline-definition-85554ec2-0ac67617.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-a043552f.js → xychartDiagram-e933f94c-c018dc37.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +2 -2
- rasa/core/channels/inspector/index.html +1 -1
- rasa/core/channels/inspector/package.json +4 -3
- rasa/core/channels/inspector/src/App.tsx +53 -7
- rasa/core/channels/inspector/src/components/Chat.tsx +3 -2
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +1 -1
- rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +268 -0
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -2
- rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +8 -3
- rasa/core/channels/inspector/src/types.ts +8 -0
- rasa/core/channels/inspector/yarn.lock +12 -12
- rasa/core/channels/studio_chat.py +119 -34
- rasa/core/channels/voice_ready/twilio_voice.py +1 -1
- rasa/core/channels/voice_stream/asr/asr_engine.py +5 -1
- rasa/core/channels/voice_stream/asr/deepgram.py +5 -0
- rasa/core/channels/voice_stream/audiocodes.py +16 -8
- rasa/core/channels/voice_stream/browser_audio.py +39 -4
- rasa/core/channels/voice_stream/call_state.py +13 -2
- rasa/core/channels/voice_stream/genesys.py +16 -13
- rasa/core/channels/voice_stream/jambonz.py +14 -12
- rasa/core/channels/voice_stream/twilio_media_streams.py +14 -13
- rasa/core/channels/voice_stream/util.py +11 -1
- rasa/core/channels/voice_stream/voice_channel.py +108 -29
- rasa/core/nlg/callback.py +1 -1
- rasa/core/nlg/contextual_response_rephraser.py +19 -9
- rasa/core/nlg/generator.py +21 -5
- rasa/core/nlg/response.py +43 -6
- rasa/core/nlg/translate.py +8 -0
- rasa/core/policies/enterprise_search_policy.py +16 -21
- rasa/dialogue_understanding/commands/correct_slots_command.py +38 -10
- rasa/dialogue_understanding/generator/command_generator.py +5 -5
- rasa/dialogue_understanding/generator/command_parser.py +9 -13
- rasa/dialogue_understanding/processor/command_processor.py +149 -55
- rasa/dialogue_understanding/stack/utils.py +13 -3
- rasa/dialogue_understanding_test/du_test_schema.yml +3 -3
- rasa/dialogue_understanding_test/validation.py +9 -10
- rasa/e2e_test/e2e_config.py +18 -11
- rasa/e2e_test/e2e_test_schema.yml +3 -3
- rasa/e2e_test/utils/validation.py +17 -19
- rasa/engine/validation.py +86 -91
- rasa/exceptions.py +26 -1
- rasa/model_manager/model_api.py +2 -2
- rasa/model_manager/socket_bridge.py +8 -2
- rasa/shared/providers/_configs/default_litellm_client_config.py +3 -7
- rasa/shared/utils/cli.py +2 -0
- rasa/shared/utils/common.py +2 -1
- rasa/shared/utils/health_check/health_check.py +10 -14
- rasa/studio/upload.py +6 -2
- rasa/studio/utils.py +33 -22
- rasa/telemetry.py +95 -22
- rasa/utils/licensing.py +21 -10
- rasa/utils/log_utils.py +1 -1
- rasa/utils/tensorflow/transformer.py +3 -3
- rasa/validator.py +7 -5
- rasa/version.py +1 -1
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/METADATA +7 -7
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/RECORD +242 -203
- rasa/builder/create_openai_vector_store.py +0 -228
- rasa/builder/llm-helper-schema.json +0 -69
- rasa/builder/llm_context.py +0 -81
- rasa/builder/llm_helper_prompt.jinja2 +0 -245
- rasa/cli/project_templates/finance/data/nlu.yml +0 -29
- rasa/cli/project_templates/finance/data/patterns/pattern_search.yml +0 -5
- rasa/cli/project_templates/finance/domain/default_flows.yml +0 -33
- rasa/cli/project_templates/finance/prompts/command-generator.jinja2 +0 -57
- rasa/cli/project_templates/finance/tests/conversation_repair/cancellations.yml +0 -12
- rasa/cli/project_templates/finance/tests/conversation_repair/cannot_handle.yml +0 -7
- rasa/cli/project_templates/finance/tests/conversation_repair/chitchat.yml +0 -7
- rasa/cli/project_templates/finance/tests/conversation_repair/clarification.yml +0 -9
- rasa/cli/project_templates/finance/tests/conversation_repair/completion.yml +0 -18
- rasa/cli/project_templates/finance/tests/conversation_repair/corrections.yml +0 -17
- rasa/cli/project_templates/finance/tests/conversation_repair/digressions.yml +0 -32
- rasa/cli/project_templates/finance/tests/conversation_repair/human_handoff.yml +0 -21
- rasa/cli/project_templates/finance/tests/conversation_repair/skipping_collect_steps.yml +0 -16
- rasa/cli/project_templates/finance/tests/demo_scripts/main.yml +0 -16
- rasa/cli/project_templates/finance/tests/happy_paths/balance_verification.yml +0 -15
- rasa/cli/project_templates/finance/tests/happy_paths/banking_questions.yml +0 -12
- rasa/cli/project_templates/finance/tests/happy_paths/card_blocking.yml +0 -52
- rasa/cli/project_templates/finance/tests/happy_paths/money_transfer.yml +0 -136
- rasa/cli/project_templates/finance/tests/happy_paths/payee_management.yml +0 -27
- rasa/cli/project_templates/finance/tests/happy_paths/user_greeted.yml +0 -5
- rasa/cli/project_templates/plain/config.yml +0 -17
- rasa/cli/project_templates/plain/data/patterns/pattern_session_start.yml +0 -7
- rasa/cli/project_templates/plain/domain.yml +0 -5
- rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +0 -1
- rasa/shared/importers/static.py +0 -63
- /rasa/{cli/project_templates/plain/actions → builder/copilot}/__init__.py +0 -0
- /rasa/builder/{inkeep-rag-response-schema.json → document_retrieval/inkeep-rag-response-schema.json} +0 -0
- /rasa/cli/project_templates/finance/actions/{action_process_immediate_payment.py → transfers/action_process_immediate_payment.py} +0 -0
- /rasa/cli/project_templates/finance/actions/{action_schedule_payment.py → transfers/action_schedule_payment.py} +0 -0
- /rasa/cli/project_templates/finance/actions/{action_validate_payment_date.py → transfers/action_validate_payment_date.py} +0 -0
- /rasa/cli/project_templates/finance/data/{flows → cards}/block_card.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → cards}/select_card.yml +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/accounts.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/advisors.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/appointments.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/branches.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/cards.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/payees.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/transactions.json +0 -0
- /rasa/cli/project_templates/finance/data/{source → system/source}/users.json +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/add_payee.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/list_payees.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/remove_payee.yml +0 -0
- /rasa/cli/project_templates/finance/data/{flows → transfers}/transfer_money.yml +0 -0
- /rasa/cli/project_templates/finance/domain/{block_card.yml → cards/block_card.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{select_card.yml → cards/select_card.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{add_payee.yml → transfers/add_payee.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{list_payees.yml → transfers/list_payees.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{remove_payee.yml → transfers/remove_payee.yml} +0 -0
- /rasa/cli/project_templates/finance/domain/{transfer_money.yml → transfers/transfer_money.yml} +0 -0
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.1a18.dist-info → rasa_pro-3.13.1a20.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Models for guardrails system."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GuardrailType(Enum):
|
|
12
|
+
"""Types of guardrails that can be applied with Lakera AI."""
|
|
13
|
+
|
|
14
|
+
PROMPT_ATTACK = "prompt_attack"
|
|
15
|
+
CONTENT_VIOLATION = "content_violation"
|
|
16
|
+
DATA_LEAKAGE = "data_leakage"
|
|
17
|
+
MALICIOUS_LINKS = "malicious_content"
|
|
18
|
+
CUSTOM = "custom"
|
|
19
|
+
OTHER = "other"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GuardrailRequest(BaseModel, ABC):
|
|
23
|
+
"""Request for guardrails check."""
|
|
24
|
+
|
|
25
|
+
hello_rasa_user_id: Optional[str] = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="Required. User identifier for the Hello Rasa project. ",
|
|
28
|
+
)
|
|
29
|
+
hello_rasa_project_id: Optional[str] = Field(
|
|
30
|
+
default=None,
|
|
31
|
+
description="Required. Project identifier for the Hello Rasa project. ",
|
|
32
|
+
)
|
|
33
|
+
metadata: Optional[Dict[str, Any]] = Field(
|
|
34
|
+
default=None, description="Additional metadata for the guardrails endpoint."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def to_json_payload(self) -> Dict[str, Any]:
|
|
39
|
+
"""Convert the request to a JSON payload."""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LakeraGuardrailRequest(GuardrailRequest):
|
|
44
|
+
"""Request for Lakera guardrails check."""
|
|
45
|
+
|
|
46
|
+
lakera_project_id: str = Field(
|
|
47
|
+
description="Required. Project identifier for the Lakera AI project."
|
|
48
|
+
)
|
|
49
|
+
payload: bool = Field(
|
|
50
|
+
default=True,
|
|
51
|
+
description=(
|
|
52
|
+
"From Lakera AI: When true the response will return a payload object "
|
|
53
|
+
"containing any PII, profanity or custom detector regex matches detected, "
|
|
54
|
+
"along with their location within the contents."
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
breakdown: bool = Field(
|
|
58
|
+
default=True,
|
|
59
|
+
description=(
|
|
60
|
+
"From Lakera AI: When true the response will return a breakdown list of "
|
|
61
|
+
"the detectors that were run, as defined in the policy, and whether each "
|
|
62
|
+
"of them detected something or not."
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
messages: List[Dict[str, Any]] = Field(
|
|
67
|
+
description=(
|
|
68
|
+
"Required. From Lakera AI: List of messages comprising the interaction "
|
|
69
|
+
"history with the LLM in OpenAI API Chat Completions format. Can be "
|
|
70
|
+
"multiple messages of any role: user, assistant, system, tool, or "
|
|
71
|
+
"developer."
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def to_json_payload(self) -> Dict[str, Any]:
|
|
76
|
+
"""Convert the request to a JSON payload to be sent to the Lakera endpoint."""
|
|
77
|
+
metadata = self.metadata or {}
|
|
78
|
+
metadata["hello_rasa_project_id"] = self.hello_rasa_project_id
|
|
79
|
+
metadata["hello_rasa_user_id"] = self.hello_rasa_user_id
|
|
80
|
+
|
|
81
|
+
json_payload: Dict[str, Any] = {
|
|
82
|
+
"messages": self.messages,
|
|
83
|
+
"project_id": self.lakera_project_id,
|
|
84
|
+
"metadata": metadata,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if self.payload:
|
|
88
|
+
json_payload["payload"] = self.payload
|
|
89
|
+
if self.breakdown:
|
|
90
|
+
json_payload["breakdown"] = self.breakdown
|
|
91
|
+
|
|
92
|
+
return json_payload
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class GuardrailDetection(BaseModel):
|
|
96
|
+
"""Represents a single guardrail detection."""
|
|
97
|
+
|
|
98
|
+
type: GuardrailType = Field(description="Type of guardrail detection.")
|
|
99
|
+
original_type: str = Field(description="Original detection from the provider.")
|
|
100
|
+
metadata: Optional[Dict[str, Any]] = Field(
|
|
101
|
+
default=None, description="Additional metadata about the detection itself."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class GuardrailResponse(BaseModel):
|
|
106
|
+
"""Response from guardrails system."""
|
|
107
|
+
|
|
108
|
+
hello_rasa_user_id: Optional[str] = Field(
|
|
109
|
+
default=None,
|
|
110
|
+
description="Required. User identifier for the Hello Rasa project. ",
|
|
111
|
+
)
|
|
112
|
+
hello_rasa_project_id: Optional[str] = Field(
|
|
113
|
+
default=None,
|
|
114
|
+
description="Required. Project identifier for the Hello Rasa project. ",
|
|
115
|
+
)
|
|
116
|
+
flagged: bool = Field(description="Whether any policy violations were detected.")
|
|
117
|
+
detections: List[GuardrailDetection] = Field(
|
|
118
|
+
default_factory=list, description="List of detected policy violations."
|
|
119
|
+
)
|
|
120
|
+
processing_time_ms: Optional[float] = Field(
|
|
121
|
+
default=None, description="Processing time in milliseconds."
|
|
122
|
+
)
|
|
123
|
+
metadata: Optional[Dict[str, Any]] = Field(
|
|
124
|
+
default=None, description="Additional metadata from the provider."
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class LakeraGuardrailResponse(GuardrailResponse):
|
|
129
|
+
"""Response from Lakera AI `/guard` endpoint."""
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_raw_response(
|
|
133
|
+
cls,
|
|
134
|
+
raw_response: Dict[str, Any],
|
|
135
|
+
hello_rasa_user_id: str,
|
|
136
|
+
hello_rasa_project_id: str,
|
|
137
|
+
) -> "LakeraGuardrailResponse":
|
|
138
|
+
"""Create a LakeraGuardrailResponse from a response."""
|
|
139
|
+
from rasa.builder.guardrails.utils import (
|
|
140
|
+
map_lakera_detector_type_to_guardrail_type,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Get the basic information from the response and create the response object.
|
|
144
|
+
flagged = raw_response.get("flagged", False)
|
|
145
|
+
metadata = raw_response.get("metadata")
|
|
146
|
+
response = cls(
|
|
147
|
+
flagged=flagged,
|
|
148
|
+
metadata=metadata,
|
|
149
|
+
hello_rasa_user_id=hello_rasa_user_id,
|
|
150
|
+
hello_rasa_project_id=hello_rasa_project_id,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# If the response is not flagged, return the response object.
|
|
154
|
+
if not flagged:
|
|
155
|
+
return response
|
|
156
|
+
|
|
157
|
+
# If the response is flagged, parse the breakdown section.
|
|
158
|
+
breakdown = raw_response.get("breakdown", [])
|
|
159
|
+
|
|
160
|
+
# Parse the breakdown.
|
|
161
|
+
detections: List[GuardrailDetection] = []
|
|
162
|
+
for detector in breakdown:
|
|
163
|
+
if detector.get("detected", True):
|
|
164
|
+
detector_type = detector.get("detector_type")
|
|
165
|
+
rasa_detection_type = map_lakera_detector_type_to_guardrail_type(
|
|
166
|
+
detector_type,
|
|
167
|
+
)
|
|
168
|
+
if not rasa_detection_type:
|
|
169
|
+
continue
|
|
170
|
+
# Remove the detector type and the detected flag from the detector.
|
|
171
|
+
# And keep the rest as part of the metadata. In case of a Lakera this
|
|
172
|
+
# will include the message_id, project_id, policy_id, detector_id, etc.
|
|
173
|
+
metadata = copy.deepcopy(detector)
|
|
174
|
+
metadata.pop("detected")
|
|
175
|
+
metadata.pop("detector_type")
|
|
176
|
+
|
|
177
|
+
detections.append(
|
|
178
|
+
GuardrailDetection(
|
|
179
|
+
type=rasa_detection_type,
|
|
180
|
+
original_type=detector_type,
|
|
181
|
+
metadata=metadata,
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# If there are detections, add them to the response.
|
|
186
|
+
if detections:
|
|
187
|
+
response.detections = detections
|
|
188
|
+
|
|
189
|
+
return response
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class GuardrailRequestKey(BaseModel):
|
|
193
|
+
user_text: str
|
|
194
|
+
hello_rasa_user_id: str = ""
|
|
195
|
+
hello_rasa_project_id: str = ""
|
|
196
|
+
lakera_project_id: str
|
|
197
|
+
|
|
198
|
+
# hashable by value
|
|
199
|
+
model_config = ConfigDict(frozen=True)
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
from rasa.builder.config import (
|
|
8
|
+
ASSISTANT_HISTORY_GUARDRAIL_PROJECT_ID,
|
|
9
|
+
COPILOT_HISTORY_GUARDRAIL_PROJECT_ID,
|
|
10
|
+
)
|
|
11
|
+
from rasa.builder.copilot.constants import ROLE_COPILOT, ROLE_USER
|
|
12
|
+
from rasa.builder.copilot.copilot_response_handler import CopilotResponseHandler
|
|
13
|
+
from rasa.builder.copilot.models import (
|
|
14
|
+
CopilotContext,
|
|
15
|
+
GeneratedContent,
|
|
16
|
+
ResponseCategory,
|
|
17
|
+
)
|
|
18
|
+
from rasa.builder.guardrails.models import (
|
|
19
|
+
GuardrailRequestKey,
|
|
20
|
+
GuardrailResponse,
|
|
21
|
+
LakeraGuardrailRequest,
|
|
22
|
+
)
|
|
23
|
+
from rasa.builder.llm_service import llm_service
|
|
24
|
+
from rasa.builder.shared.tracker_context import (
|
|
25
|
+
AssistantConversationTurn,
|
|
26
|
+
TrackerContext,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from rasa.builder.guardrails.models import GuardrailType
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
structlogger = structlog.get_logger()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def map_lakera_detector_type_to_guardrail_type(
|
|
37
|
+
lakera_detector_type: str,
|
|
38
|
+
) -> Optional["GuardrailType"]:
|
|
39
|
+
"""Map a Lakera detector type to a Rasa guardrail type."""
|
|
40
|
+
from rasa.builder.guardrails.models import GuardrailType
|
|
41
|
+
|
|
42
|
+
# Check for exact matches first
|
|
43
|
+
LAKERA_DETECTOR_TYPES_2_RASA_GUARDRAIL_TYPES_MAPPING = {
|
|
44
|
+
"prompt_attack": GuardrailType.PROMPT_ATTACK,
|
|
45
|
+
"unknown_links": GuardrailType.MALICIOUS_LINKS,
|
|
46
|
+
"custom": GuardrailType.CUSTOM,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Check for exact match first
|
|
50
|
+
if lakera_detector_type in LAKERA_DETECTOR_TYPES_2_RASA_GUARDRAIL_TYPES_MAPPING:
|
|
51
|
+
return LAKERA_DETECTOR_TYPES_2_RASA_GUARDRAIL_TYPES_MAPPING[
|
|
52
|
+
lakera_detector_type
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# Check for subtypes that start with specific prefixes
|
|
56
|
+
# https://docs.lakera.ai/docs/policies/self-hosted-policies#detectors-section
|
|
57
|
+
if lakera_detector_type.startswith("moderated_content"):
|
|
58
|
+
return GuardrailType.CONTENT_VIOLATION
|
|
59
|
+
if lakera_detector_type.startswith("pii"):
|
|
60
|
+
return GuardrailType.DATA_LEAKAGE
|
|
61
|
+
|
|
62
|
+
# If no match found, return OTHER
|
|
63
|
+
return GuardrailType.OTHER
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@lru_cache(maxsize=512)
|
|
67
|
+
def _schedule_guardrails_check(
|
|
68
|
+
user_text: str,
|
|
69
|
+
hello_rasa_user_id: str,
|
|
70
|
+
hello_rasa_project_id: str,
|
|
71
|
+
lakera_project_id: str,
|
|
72
|
+
) -> "asyncio.Task[GuardrailResponse]":
|
|
73
|
+
"""Return a cached asyncio.Task that resolves to Lakera's response.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
user_text: The user message text to check for policy violations.
|
|
77
|
+
hello_rasa_user_id: The user ID for the conversation.
|
|
78
|
+
hello_rasa_project_id: The project ID for the conversation.
|
|
79
|
+
lakera_project_id: The Lakera project ID to use for this check.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
An asyncio Task that resolves to a GuardrailResponse.
|
|
83
|
+
"""
|
|
84
|
+
structlogger.debug("guardrails.cache_miss", text=user_text)
|
|
85
|
+
|
|
86
|
+
loop = asyncio.get_running_loop()
|
|
87
|
+
request = LakeraGuardrailRequest(
|
|
88
|
+
lakera_project_id=lakera_project_id,
|
|
89
|
+
hello_rasa_user_id=hello_rasa_user_id,
|
|
90
|
+
hello_rasa_project_id=hello_rasa_project_id,
|
|
91
|
+
messages=[{"role": ROLE_USER, "content": user_text}],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return loop.create_task(llm_service.guardrails.send_request(request))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def _detect_flagged_user_indices(
|
|
98
|
+
items: List[Tuple[int, str]],
|
|
99
|
+
*,
|
|
100
|
+
hello_rasa_user_id: Optional[str],
|
|
101
|
+
hello_rasa_project_id: Optional[str],
|
|
102
|
+
lakera_project_id: str,
|
|
103
|
+
log_prefix: str,
|
|
104
|
+
) -> Set[int]:
|
|
105
|
+
"""Run guardrail checks for provided (index, user_text) pairs.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
items: List of tuples containing (index, user_text) to check.
|
|
109
|
+
hello_rasa_user_id: The user ID for the conversation.
|
|
110
|
+
hello_rasa_project_id: The project ID for the conversation.
|
|
111
|
+
lakera_project_id: The Lakera project ID to use for this check.
|
|
112
|
+
log_prefix: Prefix for logging messages.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
A set of indices that were flagged by the guardrails.
|
|
116
|
+
"""
|
|
117
|
+
if not items:
|
|
118
|
+
return set()
|
|
119
|
+
|
|
120
|
+
# 1) Group indices by logical request key (hashable by value)
|
|
121
|
+
indices_by_key: Dict[GuardrailRequestKey, List[int]] = {}
|
|
122
|
+
for idx, text in items:
|
|
123
|
+
key = GuardrailRequestKey(
|
|
124
|
+
user_text=(text or "").strip(),
|
|
125
|
+
hello_rasa_user_id=hello_rasa_user_id or "",
|
|
126
|
+
hello_rasa_project_id=hello_rasa_project_id or "",
|
|
127
|
+
lakera_project_id=lakera_project_id,
|
|
128
|
+
)
|
|
129
|
+
if not key.user_text:
|
|
130
|
+
continue
|
|
131
|
+
indices_by_key.setdefault(key, []).append(idx)
|
|
132
|
+
|
|
133
|
+
if not indices_by_key:
|
|
134
|
+
return set()
|
|
135
|
+
|
|
136
|
+
# 2) Create one task per logical key
|
|
137
|
+
tasks_by_key: Dict[GuardrailRequestKey, asyncio.Task[GuardrailResponse]] = {}
|
|
138
|
+
for key in indices_by_key:
|
|
139
|
+
tasks_by_key[key] = _schedule_guardrails_check(
|
|
140
|
+
user_text=key.user_text,
|
|
141
|
+
hello_rasa_user_id=key.hello_rasa_user_id,
|
|
142
|
+
hello_rasa_project_id=key.hello_rasa_project_id,
|
|
143
|
+
lakera_project_id=key.lakera_project_id,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# 3) Await unique tasks once
|
|
147
|
+
keys = list(tasks_by_key.keys())
|
|
148
|
+
tasks = [tasks_by_key[k] for k in keys]
|
|
149
|
+
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
|
150
|
+
|
|
151
|
+
# 4) Map results back to all corresponding indices
|
|
152
|
+
flagged: Set[int] = set()
|
|
153
|
+
for key, response in zip(keys, responses):
|
|
154
|
+
if isinstance(response, Exception):
|
|
155
|
+
structlogger.warning(f"{log_prefix}.request_failed", error=str(response))
|
|
156
|
+
continue
|
|
157
|
+
if response.flagged:
|
|
158
|
+
flagged.update(indices_by_key.get(key, []))
|
|
159
|
+
|
|
160
|
+
return flagged
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def check_assistant_chat_for_policy_violations(
|
|
164
|
+
tracker_context: TrackerContext,
|
|
165
|
+
hello_rasa_user_id: Optional[str],
|
|
166
|
+
hello_rasa_project_id: Optional[str],
|
|
167
|
+
) -> TrackerContext:
|
|
168
|
+
"""Return a sanitised TrackerContext with unsafe turns removed.
|
|
169
|
+
|
|
170
|
+
Only user messages are moderated – assistant messages are assumed safe.
|
|
171
|
+
LRU cache is used, so each unique user text is checked once.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
tracker_context: The TrackerContext containing conversation turns.
|
|
175
|
+
hello_rasa_user_id: The user ID for the conversation.
|
|
176
|
+
hello_rasa_project_id: The project ID for the conversation.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
TrackerContext with unsafe turns removed.
|
|
180
|
+
"""
|
|
181
|
+
# Collect (turn_index, user_text) for all turns with a user message
|
|
182
|
+
items: List[Tuple[int, str]] = []
|
|
183
|
+
for idx, turn in enumerate(tracker_context.conversation_turns):
|
|
184
|
+
user_message = turn.user_message
|
|
185
|
+
if not user_message:
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
text = (user_message.text or "").strip()
|
|
189
|
+
if not text:
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
items.append((idx, text))
|
|
193
|
+
|
|
194
|
+
flagged_turns = await _detect_flagged_user_indices(
|
|
195
|
+
items,
|
|
196
|
+
hello_rasa_user_id=hello_rasa_user_id,
|
|
197
|
+
hello_rasa_project_id=hello_rasa_project_id,
|
|
198
|
+
lakera_project_id=ASSISTANT_HISTORY_GUARDRAIL_PROJECT_ID,
|
|
199
|
+
log_prefix="assistant_guardrails",
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if not flagged_turns:
|
|
203
|
+
return tracker_context
|
|
204
|
+
|
|
205
|
+
structlogger.info(
|
|
206
|
+
"assistant_guardrails.turns_flagged",
|
|
207
|
+
count=len(flagged_turns),
|
|
208
|
+
turn_indices=sorted(flagged_turns),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Build a filtered TrackerContext
|
|
212
|
+
safe_turns: List[AssistantConversationTurn] = [
|
|
213
|
+
turn
|
|
214
|
+
for idx, turn in enumerate(tracker_context.conversation_turns)
|
|
215
|
+
if idx not in flagged_turns
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
new_tracker_context = tracker_context.copy(deep=True)
|
|
219
|
+
new_tracker_context.conversation_turns = safe_turns
|
|
220
|
+
return new_tracker_context
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
async def check_copilot_chat_for_policy_violations(
|
|
224
|
+
context: CopilotContext,
|
|
225
|
+
hello_rasa_user_id: Optional[str],
|
|
226
|
+
hello_rasa_project_id: Optional[str],
|
|
227
|
+
) -> Optional[GeneratedContent]:
|
|
228
|
+
"""Check the copilot chat history for guardrail policy violations.
|
|
229
|
+
|
|
230
|
+
Only user messages are moderated – assistant messages are assumed safe.
|
|
231
|
+
LRU cache is used, so each unique user text is checked once.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
context: The CopilotContext containing the copilot chat history.
|
|
235
|
+
hello_rasa_user_id: The user ID for the conversation.
|
|
236
|
+
hello_rasa_project_id: The project ID for the conversation.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Returns a default violation response if the system flags any user message,
|
|
240
|
+
otherwise return None.
|
|
241
|
+
"""
|
|
242
|
+
history = context.copilot_chat_history
|
|
243
|
+
|
|
244
|
+
# Collect (index, text) for user messages; skip ones already marked as violations
|
|
245
|
+
items: List[Tuple[int, str]] = []
|
|
246
|
+
for idx, message in enumerate(history):
|
|
247
|
+
if message.response_category == ResponseCategory.GUARDRAILS_POLICY_VIOLATION:
|
|
248
|
+
continue
|
|
249
|
+
if message.role != ROLE_USER:
|
|
250
|
+
continue
|
|
251
|
+
formatted_message = message.to_openai_format()
|
|
252
|
+
text = (formatted_message.get("content") or "").strip()
|
|
253
|
+
if not text:
|
|
254
|
+
continue
|
|
255
|
+
items.append((idx, text))
|
|
256
|
+
|
|
257
|
+
flagged_user_indices = await _detect_flagged_user_indices(
|
|
258
|
+
items,
|
|
259
|
+
hello_rasa_user_id=hello_rasa_user_id,
|
|
260
|
+
hello_rasa_project_id=hello_rasa_project_id,
|
|
261
|
+
lakera_project_id=COPILOT_HISTORY_GUARDRAIL_PROJECT_ID,
|
|
262
|
+
log_prefix="copilot_guardrails",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if not flagged_user_indices:
|
|
266
|
+
return None
|
|
267
|
+
|
|
268
|
+
# Identify the latest user message index in the current request
|
|
269
|
+
last_user_idx: Optional[int] = None
|
|
270
|
+
for i in range(len(history) - 1, -1, -1):
|
|
271
|
+
if getattr(history[i], "role", None) == ROLE_USER:
|
|
272
|
+
last_user_idx = i
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
# Remove flagged user messages and their next copilot messages
|
|
276
|
+
indices_to_remove: Set[int] = set()
|
|
277
|
+
total = len(history)
|
|
278
|
+
for uidx in flagged_user_indices:
|
|
279
|
+
indices_to_remove.add(uidx)
|
|
280
|
+
next_idx = uidx + 1
|
|
281
|
+
if (
|
|
282
|
+
next_idx < total
|
|
283
|
+
and getattr(history[next_idx], "role", None) == ROLE_COPILOT
|
|
284
|
+
):
|
|
285
|
+
indices_to_remove.add(next_idx)
|
|
286
|
+
|
|
287
|
+
# Apply sanitization
|
|
288
|
+
filtered_history = [
|
|
289
|
+
msg for i, msg in enumerate(history) if i not in indices_to_remove
|
|
290
|
+
]
|
|
291
|
+
if len(filtered_history) != len(history):
|
|
292
|
+
structlogger.info(
|
|
293
|
+
"copilot_guardrails.history_sanitized",
|
|
294
|
+
removed_indices=sorted(indices_to_remove),
|
|
295
|
+
removed_messages=len(history) - len(filtered_history),
|
|
296
|
+
kept_messages=len(filtered_history),
|
|
297
|
+
)
|
|
298
|
+
context.copilot_chat_history = filtered_history
|
|
299
|
+
|
|
300
|
+
# Block only if the latest user message in this request was flagged
|
|
301
|
+
if last_user_idx is not None and last_user_idx in flagged_user_indices:
|
|
302
|
+
return CopilotResponseHandler.respond_to_guardrail_policy_violations()
|
|
303
|
+
|
|
304
|
+
# Otherwise proceed (following messages are respected)
|
|
305
|
+
return None
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import AsyncGenerator, ClassVar, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
7
|
+
|
|
8
|
+
from rasa.builder.models import JobStatusEvent, ServerSentEventType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class JobInfo(BaseModel):
|
|
12
|
+
"""Information about a job being processed by the worker."""
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
status: str = ""
|
|
16
|
+
error: Optional[str] = None
|
|
17
|
+
created_at: float = Field(default_factory=time.time)
|
|
18
|
+
|
|
19
|
+
_history: List[JobStatusEvent] = PrivateAttr(default_factory=list)
|
|
20
|
+
_queue: asyncio.Queue = PrivateAttr(default_factory=asyncio.Queue)
|
|
21
|
+
|
|
22
|
+
class Config:
|
|
23
|
+
arbitrary_types_allowed = True
|
|
24
|
+
|
|
25
|
+
async def put(self, event: JobStatusEvent) -> None:
|
|
26
|
+
"""Put an event onto the job's queue.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
event: The JobStatusEvent to put onto the queue.
|
|
30
|
+
"""
|
|
31
|
+
await self._queue.put(event)
|
|
32
|
+
|
|
33
|
+
async def event_stream(self) -> AsyncGenerator[JobStatusEvent, None]:
|
|
34
|
+
"""Yield events as they are put on the queue by the worker task.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
An async generator that yields JobStatusEvent objects.
|
|
38
|
+
"""
|
|
39
|
+
# 1) Replay history and stop if EOF recorded
|
|
40
|
+
for sse in self._history:
|
|
41
|
+
if (
|
|
42
|
+
isinstance(sse, JobStatusEvent)
|
|
43
|
+
and sse.event == ServerSentEventType._EOF.value
|
|
44
|
+
):
|
|
45
|
+
return
|
|
46
|
+
yield sse
|
|
47
|
+
|
|
48
|
+
# 2) Stream live events and stop on EOF
|
|
49
|
+
while True:
|
|
50
|
+
sse = await self._queue.get()
|
|
51
|
+
self._history.append(sse)
|
|
52
|
+
if (
|
|
53
|
+
isinstance(sse, JobStatusEvent)
|
|
54
|
+
and sse.event == ServerSentEventType._EOF.value
|
|
55
|
+
):
|
|
56
|
+
return
|
|
57
|
+
yield sse
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class JobManager:
|
|
61
|
+
"""Very small in-memory job registry (single-process only)."""
|
|
62
|
+
|
|
63
|
+
_jobs: ClassVar[Dict[str, JobInfo]] = {}
|
|
64
|
+
|
|
65
|
+
def create_job(self) -> JobInfo:
|
|
66
|
+
job_id = uuid.uuid4().hex
|
|
67
|
+
job = JobInfo(id=job_id)
|
|
68
|
+
self._jobs[job_id] = job
|
|
69
|
+
return job
|
|
70
|
+
|
|
71
|
+
def get_job(self, job_id: str) -> Optional[JobInfo]:
|
|
72
|
+
return self._jobs.get(job_id)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def mark_done(job: JobInfo, *, error: Optional[str] = None) -> None:
|
|
76
|
+
"""Mark a job as done.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
job: The JobInfo instance to mark as done.
|
|
80
|
+
error: Optional error message if the job failed.
|
|
81
|
+
"""
|
|
82
|
+
job.error = error
|
|
83
|
+
eof = JobStatusEvent.eof()
|
|
84
|
+
job._queue.put_nowait(eof)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
job_manager = JobManager()
|