rasa-pro 3.9.18__py3-none-any.whl → 3.10.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- README.md +26 -57
- rasa/__init__.py +1 -2
- rasa/__main__.py +5 -0
- rasa/anonymization/anonymization_rule_executor.py +2 -2
- rasa/api.py +26 -22
- rasa/cli/arguments/data.py +27 -2
- rasa/cli/arguments/default_arguments.py +25 -3
- rasa/cli/arguments/run.py +9 -9
- rasa/cli/arguments/train.py +2 -0
- rasa/cli/data.py +70 -8
- rasa/cli/e2e_test.py +108 -433
- rasa/cli/interactive.py +1 -0
- rasa/cli/llm_fine_tuning.py +395 -0
- rasa/cli/project_templates/calm/endpoints.yml +1 -1
- rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
- rasa/cli/run.py +14 -13
- rasa/cli/scaffold.py +10 -8
- rasa/cli/train.py +8 -7
- rasa/cli/utils.py +15 -0
- rasa/constants.py +7 -1
- rasa/core/actions/action.py +98 -49
- rasa/core/actions/action_run_slot_rejections.py +4 -1
- rasa/core/actions/custom_action_executor.py +9 -6
- rasa/core/actions/direct_custom_actions_executor.py +80 -0
- rasa/core/actions/e2e_stub_custom_action_executor.py +68 -0
- rasa/core/actions/grpc_custom_action_executor.py +2 -2
- rasa/core/actions/http_custom_action_executor.py +6 -5
- rasa/core/agent.py +21 -17
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/audiocodes.py +1 -16
- rasa/core/channels/inspector/dist/index.html +0 -2
- rasa/core/channels/inspector/index.html +0 -2
- rasa/core/channels/voice_aware/__init__.py +0 -0
- rasa/core/channels/voice_aware/jambonz.py +103 -0
- rasa/core/channels/voice_aware/jambonz_protocol.py +344 -0
- rasa/core/channels/voice_aware/utils.py +20 -0
- rasa/core/channels/voice_native/__init__.py +0 -0
- rasa/core/constants.py +6 -1
- rasa/core/featurizers/single_state_featurizer.py +1 -22
- rasa/core/featurizers/tracker_featurizers.py +18 -115
- rasa/core/information_retrieval/faiss.py +7 -4
- rasa/core/information_retrieval/information_retrieval.py +8 -0
- rasa/core/information_retrieval/milvus.py +9 -2
- rasa/core/information_retrieval/qdrant.py +1 -1
- rasa/core/nlg/contextual_response_rephraser.py +32 -10
- rasa/core/nlg/summarize.py +4 -3
- rasa/core/policies/enterprise_search_policy.py +100 -44
- rasa/core/policies/flows/flow_executor.py +130 -94
- rasa/core/policies/intentless_policy.py +52 -28
- rasa/core/policies/ted_policy.py +33 -58
- rasa/core/policies/unexpected_intent_policy.py +7 -15
- rasa/core/processor.py +20 -53
- rasa/core/run.py +5 -4
- rasa/core/tracker_store.py +8 -4
- rasa/core/utils.py +45 -56
- rasa/dialogue_understanding/coexistence/llm_based_router.py +45 -12
- rasa/dialogue_understanding/commands/__init__.py +4 -0
- rasa/dialogue_understanding/commands/change_flow_command.py +0 -6
- rasa/dialogue_understanding/commands/session_start_command.py +59 -0
- rasa/dialogue_understanding/commands/set_slot_command.py +1 -5
- rasa/dialogue_understanding/commands/utils.py +38 -0
- rasa/dialogue_understanding/generator/constants.py +10 -3
- rasa/dialogue_understanding/generator/flow_retrieval.py +14 -5
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +12 -2
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +106 -87
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +28 -6
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +90 -37
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +15 -15
- rasa/dialogue_understanding/patterns/session_start.py +37 -0
- rasa/dialogue_understanding/processor/command_processor.py +13 -14
- rasa/e2e_test/aggregate_test_stats_calculator.py +124 -0
- rasa/e2e_test/assertions.py +1181 -0
- rasa/e2e_test/assertions_schema.yml +106 -0
- rasa/e2e_test/constants.py +20 -0
- rasa/e2e_test/e2e_config.py +220 -0
- rasa/e2e_test/e2e_config_schema.yml +26 -0
- rasa/e2e_test/e2e_test_case.py +131 -8
- rasa/e2e_test/e2e_test_converter.py +363 -0
- rasa/e2e_test/e2e_test_converter_prompt.jinja2 +70 -0
- rasa/e2e_test/e2e_test_coverage_report.py +364 -0
- rasa/e2e_test/e2e_test_result.py +26 -6
- rasa/e2e_test/e2e_test_runner.py +491 -72
- rasa/e2e_test/e2e_test_schema.yml +96 -0
- rasa/e2e_test/pykwalify_extensions.py +39 -0
- rasa/e2e_test/stub_custom_action.py +70 -0
- rasa/e2e_test/utils/__init__.py +0 -0
- rasa/e2e_test/utils/e2e_yaml_utils.py +55 -0
- rasa/e2e_test/utils/io.py +596 -0
- rasa/e2e_test/utils/validation.py +80 -0
- rasa/engine/recipes/default_components.py +0 -2
- rasa/engine/storage/local_model_storage.py +0 -1
- rasa/env.py +9 -0
- rasa/keys +1 -0
- rasa/llm_fine_tuning/__init__.py +0 -0
- rasa/llm_fine_tuning/annotation_module.py +241 -0
- rasa/llm_fine_tuning/conversations.py +144 -0
- rasa/llm_fine_tuning/llm_data_preparation_module.py +178 -0
- rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
- rasa/llm_fine_tuning/paraphrasing/__init__.py +0 -0
- rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +281 -0
- rasa/llm_fine_tuning/paraphrasing/default_rephrase_prompt_template.jina2 +44 -0
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +121 -0
- rasa/llm_fine_tuning/paraphrasing/rephrased_user_message.py +10 -0
- rasa/llm_fine_tuning/paraphrasing_module.py +128 -0
- rasa/llm_fine_tuning/storage.py +174 -0
- rasa/llm_fine_tuning/train_test_split_module.py +441 -0
- rasa/model_training.py +48 -16
- rasa/nlu/classifiers/diet_classifier.py +25 -38
- rasa/nlu/classifiers/logistic_regression_classifier.py +9 -44
- rasa/nlu/classifiers/sklearn_intent_classifier.py +16 -37
- rasa/nlu/extractors/crf_entity_extractor.py +50 -93
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +45 -78
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +17 -52
- rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +3 -5
- rasa/nlu/persistor.py +129 -32
- rasa/server.py +45 -10
- rasa/shared/constants.py +63 -15
- rasa/shared/core/domain.py +15 -12
- rasa/shared/core/events.py +28 -2
- rasa/shared/core/flows/flow.py +208 -13
- rasa/shared/core/flows/flow_path.py +84 -0
- rasa/shared/core/flows/flows_list.py +28 -10
- rasa/shared/core/flows/flows_yaml_schema.json +269 -193
- rasa/shared/core/flows/validation.py +112 -25
- rasa/shared/core/flows/yaml_flows_io.py +149 -10
- rasa/shared/core/trackers.py +6 -0
- rasa/shared/core/training_data/visualization.html +2 -2
- rasa/shared/exceptions.py +4 -0
- rasa/shared/importers/importer.py +60 -11
- rasa/shared/importers/remote_importer.py +196 -0
- rasa/shared/nlu/constants.py +2 -0
- rasa/shared/nlu/training_data/features.py +2 -120
- rasa/shared/providers/_configs/__init__.py +0 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +181 -0
- rasa/shared/providers/_configs/client_config.py +57 -0
- rasa/shared/providers/_configs/default_litellm_client_config.py +130 -0
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +234 -0
- rasa/shared/providers/_configs/openai_client_config.py +175 -0
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +171 -0
- rasa/shared/providers/_configs/utils.py +101 -0
- rasa/shared/providers/_ssl_verification_utils.py +124 -0
- rasa/shared/providers/embedding/__init__.py +0 -0
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +254 -0
- rasa/shared/providers/embedding/_langchain_embedding_client_adapter.py +74 -0
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +277 -0
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +102 -0
- rasa/shared/providers/embedding/embedding_client.py +90 -0
- rasa/shared/providers/embedding/embedding_response.py +41 -0
- rasa/shared/providers/embedding/huggingface_local_embedding_client.py +191 -0
- rasa/shared/providers/embedding/openai_embedding_client.py +172 -0
- rasa/shared/providers/llm/__init__.py +0 -0
- rasa/shared/providers/llm/_base_litellm_client.py +227 -0
- rasa/shared/providers/llm/azure_openai_llm_client.py +338 -0
- rasa/shared/providers/llm/default_litellm_llm_client.py +84 -0
- rasa/shared/providers/llm/llm_client.py +76 -0
- rasa/shared/providers/llm/llm_response.py +50 -0
- rasa/shared/providers/llm/openai_llm_client.py +155 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +169 -0
- rasa/shared/providers/mappings.py +75 -0
- rasa/shared/utils/cli.py +30 -0
- rasa/shared/utils/io.py +65 -3
- rasa/shared/utils/llm.py +223 -200
- rasa/shared/utils/yaml.py +122 -7
- rasa/studio/download.py +19 -13
- rasa/studio/train.py +2 -3
- rasa/studio/upload.py +2 -3
- rasa/telemetry.py +113 -58
- rasa/tracing/config.py +2 -3
- rasa/tracing/instrumentation/attribute_extractors.py +29 -17
- rasa/tracing/instrumentation/instrumentation.py +4 -47
- rasa/utils/common.py +18 -19
- rasa/utils/endpoints.py +7 -4
- rasa/utils/io.py +66 -0
- rasa/utils/json_utils.py +60 -0
- rasa/utils/licensing.py +9 -1
- rasa/utils/ml_utils.py +4 -2
- rasa/utils/tensorflow/model_data.py +193 -2
- rasa/validator.py +196 -1
- rasa/version.py +1 -1
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/METADATA +47 -72
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/RECORD +186 -121
- rasa/nlu/classifiers/llm_intent_classifier.py +0 -519
- rasa/shared/providers/openai/clients.py +0 -43
- rasa/shared/providers/openai/session_handler.py +0 -110
- rasa/utils/tensorflow/feature_array.py +0 -366
- /rasa/{shared/providers/openai → cli/project_templates/tutorial/actions}/__init__.py +0 -0
- /rasa/cli/project_templates/tutorial/{actions.py → actions/actions.py} +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/NOTICE +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/WHEEL +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/entry_points.txt +0 -0
rasa/core/agent.py
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import functools
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
+
import uuid
|
|
7
|
+
from asyncio import AbstractEventLoop, CancelledError
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any, Callable, Dict, List, Optional, Text, Union
|
|
8
|
-
import uuid
|
|
9
10
|
|
|
10
11
|
import aiohttp
|
|
11
12
|
from aiohttp import ClientError
|
|
12
13
|
|
|
14
|
+
import rasa.shared.utils.io
|
|
13
15
|
from rasa.core import jobs
|
|
14
16
|
from rasa.core.channels.channel import OutputChannel, UserMessage
|
|
15
17
|
from rasa.core.constants import DEFAULT_REQUEST_TIMEOUT
|
|
16
|
-
from rasa.core.http_interpreter import RasaNLUHttpInterpreter
|
|
17
|
-
from rasa.shared.core.domain import Domain
|
|
18
18
|
from rasa.core.exceptions import AgentNotReady
|
|
19
|
-
from rasa.
|
|
19
|
+
from rasa.core.http_interpreter import RasaNLUHttpInterpreter
|
|
20
20
|
from rasa.core.lock_store import InMemoryLockStore, LockStore
|
|
21
21
|
from rasa.core.nlg import NaturalLanguageGenerator, TemplatedNaturalLanguageGenerator
|
|
22
22
|
from rasa.core.policies.policy import PolicyPrediction
|
|
23
23
|
from rasa.core.processor import MessageProcessor
|
|
24
|
-
from rasa.core.tracker_store import
|
|
25
|
-
|
|
24
|
+
from rasa.core.tracker_store import (
|
|
25
|
+
FailSafeTrackerStore,
|
|
26
|
+
InMemoryTrackerStore,
|
|
27
|
+
TrackerStore,
|
|
28
|
+
)
|
|
29
|
+
from rasa.core.utils import AvailableEndpoints
|
|
26
30
|
from rasa.exceptions import ModelNotFound
|
|
31
|
+
from rasa.nlu.persistor import StorageType
|
|
27
32
|
from rasa.nlu.utils import is_url
|
|
33
|
+
from rasa.shared.constants import DEFAULT_SENDER_ID
|
|
34
|
+
from rasa.shared.core.domain import Domain
|
|
35
|
+
from rasa.shared.core.trackers import DialogueStateTracker, EventVerbosity
|
|
28
36
|
from rasa.shared.exceptions import RasaException
|
|
29
|
-
import rasa.shared.utils.io
|
|
30
37
|
from rasa.utils.common import TempDirectoryPath, get_temp_dir_name
|
|
31
38
|
from rasa.utils.endpoints import EndpointConfig
|
|
32
39
|
|
|
33
|
-
from rasa.core.tracker_store import TrackerStore
|
|
34
|
-
from rasa.core.utils import AvailableEndpoints
|
|
35
|
-
|
|
36
40
|
logger = logging.getLogger(__name__)
|
|
37
41
|
|
|
38
42
|
|
|
@@ -194,7 +198,7 @@ async def _schedule_model_pulling(
|
|
|
194
198
|
async def load_agent(
|
|
195
199
|
model_path: Optional[Text] = None,
|
|
196
200
|
model_server: Optional[EndpointConfig] = None,
|
|
197
|
-
remote_storage: Optional[
|
|
201
|
+
remote_storage: Optional[StorageType] = None,
|
|
198
202
|
endpoints: Optional[AvailableEndpoints] = None,
|
|
199
203
|
loop: Optional[AbstractEventLoop] = None,
|
|
200
204
|
) -> Agent:
|
|
@@ -203,15 +207,15 @@ async def load_agent(
|
|
|
203
207
|
Args:
|
|
204
208
|
model_path: Path to the model if it's on disk.
|
|
205
209
|
model_server: Configuration for a potential server which serves the model.
|
|
206
|
-
remote_storage:
|
|
210
|
+
remote_storage: Remote storage to use for loading the model.
|
|
207
211
|
endpoints: Endpoint configuration.
|
|
208
212
|
loop: Optional async loop to pass to broker creation.
|
|
209
213
|
|
|
210
214
|
Returns:
|
|
211
215
|
The instantiated `Agent` or `None`.
|
|
212
216
|
"""
|
|
213
|
-
from rasa.core.tracker_store import TrackerStore
|
|
214
217
|
from rasa.core.brokers.broker import EventBroker
|
|
218
|
+
from rasa.core.tracker_store import TrackerStore
|
|
215
219
|
|
|
216
220
|
tracker_store = None
|
|
217
221
|
lock_store = None
|
|
@@ -299,7 +303,7 @@ class Agent:
|
|
|
299
303
|
action_endpoint: Optional[EndpointConfig] = None,
|
|
300
304
|
fingerprint: Optional[Text] = None,
|
|
301
305
|
model_server: Optional[EndpointConfig] = None,
|
|
302
|
-
remote_storage: Optional[
|
|
306
|
+
remote_storage: Optional[StorageType] = None,
|
|
303
307
|
http_interpreter: Optional[RasaNLUHttpInterpreter] = None,
|
|
304
308
|
endpoints: Optional[AvailableEndpoints] = None,
|
|
305
309
|
):
|
|
@@ -329,11 +333,11 @@ class Agent:
|
|
|
329
333
|
action_endpoint: Optional[EndpointConfig] = None,
|
|
330
334
|
fingerprint: Optional[Text] = None,
|
|
331
335
|
model_server: Optional[EndpointConfig] = None,
|
|
332
|
-
remote_storage: Optional[
|
|
336
|
+
remote_storage: Optional[StorageType] = None,
|
|
333
337
|
http_interpreter: Optional[RasaNLUHttpInterpreter] = None,
|
|
334
338
|
endpoints: Optional[AvailableEndpoints] = None,
|
|
335
339
|
) -> Agent:
|
|
336
|
-
"""Constructs a new agent and loads the
|
|
340
|
+
"""Constructs a new agent and loads the processor and model."""
|
|
337
341
|
agent = Agent(
|
|
338
342
|
domain=domain,
|
|
339
343
|
generator=generator,
|
rasa/core/channels/__init__.py
CHANGED
|
@@ -22,6 +22,7 @@ from rasa.core.channels.slack import SlackInput
|
|
|
22
22
|
from rasa.core.channels.telegram import TelegramInput
|
|
23
23
|
from rasa.core.channels.twilio import TwilioInput
|
|
24
24
|
from rasa.core.channels.twilio_voice import TwilioVoiceInput
|
|
25
|
+
from rasa.core.channels.voice_aware.jambonz import JambonzVoiceAwareInput
|
|
25
26
|
from rasa.core.channels.webexteams import WebexTeamsInput
|
|
26
27
|
from rasa.core.channels.hangouts import HangoutsInput
|
|
27
28
|
from rasa.core.channels.audiocodes import AudiocodesInput
|
|
@@ -47,6 +48,7 @@ input_channel_classes: List[Type[InputChannel]] = [
|
|
|
47
48
|
AudiocodesInput,
|
|
48
49
|
DevelopmentInspectInput,
|
|
49
50
|
CVGInput,
|
|
51
|
+
JambonzVoiceAwareInput,
|
|
50
52
|
]
|
|
51
53
|
|
|
52
54
|
# Mapping from an input channel name to its class to allow name based lookup.
|
rasa/core/channels/audiocodes.py
CHANGED
|
@@ -9,6 +9,7 @@ import structlog
|
|
|
9
9
|
from jsonschema import ValidationError, validate
|
|
10
10
|
from rasa.core import jobs
|
|
11
11
|
from rasa.core.channels.channel import InputChannel, OutputChannel, UserMessage
|
|
12
|
+
from rasa.core.channels.voice_aware.utils import validate_voice_license_scope
|
|
12
13
|
from rasa.shared.constants import INTENT_MESSAGE_PREFIX
|
|
13
14
|
from rasa.shared.exceptions import RasaException
|
|
14
15
|
from sanic import Blueprint, response
|
|
@@ -16,11 +17,6 @@ from sanic.exceptions import NotFound, SanicException, ServerError
|
|
|
16
17
|
from sanic.request import Request
|
|
17
18
|
from sanic.response import HTTPResponse
|
|
18
19
|
|
|
19
|
-
from rasa.utils.licensing import (
|
|
20
|
-
PRODUCT_AREA,
|
|
21
|
-
VOICE_SCOPE,
|
|
22
|
-
validate_license_from_env,
|
|
23
|
-
)
|
|
24
20
|
|
|
25
21
|
logger = logging.getLogger(__name__)
|
|
26
22
|
structlogger = structlog.get_logger()
|
|
@@ -30,17 +26,6 @@ KEEP_ALIVE_SECONDS = 120
|
|
|
30
26
|
KEEP_ALIVE_EXPIRATION_FACTOR = 1.5
|
|
31
27
|
|
|
32
28
|
|
|
33
|
-
def validate_voice_license_scope() -> None:
|
|
34
|
-
"""Validate that the correct license scope is present."""
|
|
35
|
-
logger.info(
|
|
36
|
-
f"Validating current Rasa Pro license scope which must include "
|
|
37
|
-
f"the '{VOICE_SCOPE}' scope to use the voice channel."
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
voice_product_scope = PRODUCT_AREA + " " + VOICE_SCOPE
|
|
41
|
-
validate_license_from_env(product_area=voice_product_scope)
|
|
42
|
-
|
|
43
|
-
|
|
44
29
|
class Unauthorized(SanicException):
|
|
45
30
|
"""**Status**: 401 Not Authorized."""
|
|
46
31
|
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
<script>
|
|
18
18
|
const chatDiv = document.getElementById("rasa-chat-widget");
|
|
19
19
|
const websocketUrl = window.location.origin.replace("http", "ws");
|
|
20
|
-
const initialPayload = "/session_start";
|
|
21
20
|
const maxHeight = document.documentElement.scrollHeight - 130;
|
|
22
21
|
// 21 and 25 are the rem number we're using for the columns. We add 0.75rem for the padding
|
|
23
22
|
// A potential improvement would be to add a onresize event for both width and height
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
const columnWidth = remReference * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
30
29
|
|
|
31
30
|
chatDiv.setAttribute("data-websocket-url", websocketUrl);
|
|
32
|
-
chatDiv.setAttribute("data-initial-payload", initialPayload);
|
|
33
31
|
chatDiv.setAttribute("data-close-on-outside-click", false);
|
|
34
32
|
chatDiv.setAttribute("data-height", maxHeight);
|
|
35
33
|
chatDiv.setAttribute("data-width", columnWidth);
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
<script>
|
|
16
16
|
const chatDiv = document.getElementById("rasa-chat-widget");
|
|
17
17
|
const websocketUrl = window.location.origin.replace("http", "ws");
|
|
18
|
-
const initialPayload = "/session_start";
|
|
19
18
|
const maxHeight = document.documentElement.scrollHeight - 130;
|
|
20
19
|
// 21 and 25 are the rem number we're using for the columns. We add 0.75rem for the padding
|
|
21
20
|
// A potential improvement would be to add a onresize event for both width and height
|
|
@@ -27,7 +26,6 @@
|
|
|
27
26
|
const columnWidth = remReference * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
28
27
|
|
|
29
28
|
chatDiv.setAttribute("data-websocket-url", websocketUrl);
|
|
30
|
-
chatDiv.setAttribute("data-initial-payload", initialPayload);
|
|
31
29
|
chatDiv.setAttribute("data-close-on-outside-click", false);
|
|
32
30
|
chatDiv.setAttribute("data-height", maxHeight);
|
|
33
31
|
chatDiv.setAttribute("data-width", columnWidth);
|
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, Text
|
|
2
|
+
|
|
3
|
+
import structlog
|
|
4
|
+
from rasa.core.channels.channel import InputChannel, OutputChannel, UserMessage
|
|
5
|
+
from rasa.core.channels.voice_aware.jambonz_protocol import (
|
|
6
|
+
send_ws_text_message,
|
|
7
|
+
websocket_message_handler,
|
|
8
|
+
)
|
|
9
|
+
from rasa.core.channels.voice_aware.utils import validate_voice_license_scope
|
|
10
|
+
from rasa.shared.exceptions import RasaException
|
|
11
|
+
from sanic import Blueprint, response, Websocket # type: ignore[attr-defined]
|
|
12
|
+
from sanic.request import Request
|
|
13
|
+
from sanic.response import HTTPResponse
|
|
14
|
+
|
|
15
|
+
from rasa.shared.utils.common import mark_as_experimental_feature
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
structlogger = structlog.get_logger()
|
|
19
|
+
|
|
20
|
+
CHANNEL_NAME = "jambonz"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class JambonzVoiceAwareInput(InputChannel):
|
|
24
|
+
"""Connector for the Jambonz platform."""
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def name(cls) -> Text:
|
|
28
|
+
return CHANNEL_NAME
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_credentials(cls, credentials: Optional[Dict[Text, Any]]) -> InputChannel:
|
|
32
|
+
return cls()
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
"""Initializes the JambonzVoiceAwareInput channel."""
|
|
36
|
+
mark_as_experimental_feature("Jambonz Channel")
|
|
37
|
+
validate_voice_license_scope()
|
|
38
|
+
|
|
39
|
+
def blueprint(
|
|
40
|
+
self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
|
|
41
|
+
) -> Blueprint:
|
|
42
|
+
jambonz_webhook = Blueprint("jambonz_webhook", __name__)
|
|
43
|
+
|
|
44
|
+
@jambonz_webhook.route("/", methods=["GET"])
|
|
45
|
+
async def health(request: Request) -> HTTPResponse:
|
|
46
|
+
"""Server health route."""
|
|
47
|
+
return response.json({"status": "ok"})
|
|
48
|
+
|
|
49
|
+
@jambonz_webhook.websocket("/websocket", subprotocols=["ws.jambonz.org"]) # type: ignore
|
|
50
|
+
async def websocket(request: Request, ws: Websocket) -> None:
|
|
51
|
+
"""Triggered on new websocket connection."""
|
|
52
|
+
async for message in ws:
|
|
53
|
+
await websocket_message_handler(message, on_new_message, ws)
|
|
54
|
+
|
|
55
|
+
return jambonz_webhook
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class JambonzWebsocketOutput(OutputChannel):
|
|
59
|
+
@classmethod
|
|
60
|
+
def name(cls) -> Text:
|
|
61
|
+
return CHANNEL_NAME
|
|
62
|
+
|
|
63
|
+
def __init__(self, ws: Any, conversation_id: Text) -> None:
|
|
64
|
+
self.ws = ws
|
|
65
|
+
self.conversation_id = conversation_id
|
|
66
|
+
|
|
67
|
+
async def add_message(self, message: Dict) -> None:
|
|
68
|
+
"""Add metadata and add message.
|
|
69
|
+
|
|
70
|
+
Message is added to the list of
|
|
71
|
+
activities to be sent to the Jambonz Websocket server.
|
|
72
|
+
"""
|
|
73
|
+
text_message = message.get("text", "")
|
|
74
|
+
structlogger.debug(
|
|
75
|
+
"jambonz.add.message",
|
|
76
|
+
class_name=self.__class__.__name__,
|
|
77
|
+
message=text_message,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# send message to jambonz
|
|
81
|
+
await send_ws_text_message(self.ws, message.get("text"))
|
|
82
|
+
|
|
83
|
+
async def send_text_message(
|
|
84
|
+
self, recipient_id: Text, text: Text, **kwargs: Any
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Send a text message."""
|
|
87
|
+
await self.add_message({"type": "message", "text": text})
|
|
88
|
+
|
|
89
|
+
async def send_image_url(
|
|
90
|
+
self, recipient_id: Text, image: Text, **kwargs: Any
|
|
91
|
+
) -> None:
|
|
92
|
+
raise RasaException("Images are not supported by this channel")
|
|
93
|
+
|
|
94
|
+
async def send_attachment(
|
|
95
|
+
self, recipient_id: Text, attachment: Text, **kwargs: Any
|
|
96
|
+
) -> None:
|
|
97
|
+
raise RasaException("Attachments are not supported by this channel")
|
|
98
|
+
|
|
99
|
+
async def send_custom_json(
|
|
100
|
+
self, recipient_id: Text, json_message: Dict[Text, Any], **kwargs: Any
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Send an activity."""
|
|
103
|
+
await self.add_message(json_message)
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
import json
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Any, Awaitable, Callable, Dict, List, Text
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
from rasa.core.channels.channel import UserMessage
|
|
8
|
+
from sanic import Websocket # type: ignore[attr-defined]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
structlogger = structlog.get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class NewSessionMessage:
|
|
16
|
+
"""Message indicating a new session has been started."""
|
|
17
|
+
|
|
18
|
+
call_sid: str
|
|
19
|
+
message_id: str
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_message(message: Dict[str, Any]) -> "NewSessionMessage":
|
|
23
|
+
return NewSessionMessage(
|
|
24
|
+
message.get("call_sid"),
|
|
25
|
+
message.get("msgid"),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Transcript:
|
|
31
|
+
"""Transcript of a spoken utterance."""
|
|
32
|
+
|
|
33
|
+
text: str
|
|
34
|
+
confidence: float
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class TranscriptResult:
|
|
39
|
+
"""Result of an ASR call with potential transcripts."""
|
|
40
|
+
|
|
41
|
+
call_sid: str
|
|
42
|
+
message_id: str
|
|
43
|
+
is_final: bool
|
|
44
|
+
transcripts: List[Transcript] = field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def from_speech_result(message: Dict[str, Any]) -> "TranscriptResult":
|
|
48
|
+
return TranscriptResult(
|
|
49
|
+
message.get("call_sid"),
|
|
50
|
+
message.get("msgid"),
|
|
51
|
+
message.get("data", {}).get("speech", {}).get("is_final", True),
|
|
52
|
+
transcripts=[
|
|
53
|
+
Transcript(t.get("transcript", ""), t.get("confidence", 1.0))
|
|
54
|
+
for t in message.get("data", {})
|
|
55
|
+
.get("speech", {})
|
|
56
|
+
.get("alternatives", [])
|
|
57
|
+
],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def from_dtmf_result(message: Dict[str, Any]) -> "TranscriptResult":
|
|
62
|
+
"""Create a transcript result from a DTMF result.
|
|
63
|
+
|
|
64
|
+
We use the dtmf as the text with confidence 1.0
|
|
65
|
+
"""
|
|
66
|
+
return TranscriptResult(
|
|
67
|
+
message.get("call_sid"),
|
|
68
|
+
message.get("msgid"),
|
|
69
|
+
is_final=True,
|
|
70
|
+
transcripts=[
|
|
71
|
+
Transcript(str(message.get("data", {}).get("digits", "")), 1.0)
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class CallStatusChanged:
|
|
78
|
+
"""Message indicating a change in the call status."""
|
|
79
|
+
|
|
80
|
+
call_sid: str
|
|
81
|
+
status: str
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def from_message(message: Dict[str, Any]) -> "CallStatusChanged":
|
|
85
|
+
return CallStatusChanged(
|
|
86
|
+
message.get("call_sid"), message.get("data", {}).get("call_status")
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class SessionReconnect:
|
|
92
|
+
"""Message indicating a session has reconnected."""
|
|
93
|
+
|
|
94
|
+
call_sid: str
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def from_message(message: Dict[str, Any]) -> "SessionReconnect":
|
|
98
|
+
return SessionReconnect(message.get("call_sid"))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class VerbStatusChanged:
|
|
103
|
+
"""Message indicating a change in the status of a verb."""
|
|
104
|
+
|
|
105
|
+
call_sid: str
|
|
106
|
+
event: str
|
|
107
|
+
id: str
|
|
108
|
+
name: str
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def from_message(message: Dict[str, Any]) -> "VerbStatusChanged":
|
|
112
|
+
return VerbStatusChanged(
|
|
113
|
+
message.get("call_sid"),
|
|
114
|
+
message.get("data", {}).get("event"),
|
|
115
|
+
message.get("data", {}).get("id"),
|
|
116
|
+
message.get("data", {}).get("name"),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass
|
|
121
|
+
class GatherTimeout:
|
|
122
|
+
"""Message indicating a gather timeout."""
|
|
123
|
+
|
|
124
|
+
call_sid: str
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def from_message(message: Dict[str, Any]) -> "GatherTimeout":
|
|
128
|
+
return GatherTimeout(message.get("call_sid"))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def websocket_message_handler(
|
|
132
|
+
message_dump: str,
|
|
133
|
+
on_new_message: Callable[[UserMessage], Awaitable[Any]],
|
|
134
|
+
ws: Websocket,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Handle incoming messages from the websocket."""
|
|
137
|
+
message = json.loads(message_dump)
|
|
138
|
+
|
|
139
|
+
# parse and handle the different message types
|
|
140
|
+
if message.get("type") == "session:new":
|
|
141
|
+
new_session = NewSessionMessage.from_message(message)
|
|
142
|
+
await handle_new_session(new_session, on_new_message, ws)
|
|
143
|
+
elif message.get("type") == "session:reconnect":
|
|
144
|
+
session_reconnect = SessionReconnect.from_message(message)
|
|
145
|
+
await handle_session_reconnect(session_reconnect)
|
|
146
|
+
elif message.get("type") == "call:status":
|
|
147
|
+
call_status = CallStatusChanged.from_message(message)
|
|
148
|
+
await handle_call_status(call_status)
|
|
149
|
+
elif message.get("type") == "verb:hook" and message.get("hook") == "/gather":
|
|
150
|
+
hook_trigger_reason = message.get("data", {}).get("reason")
|
|
151
|
+
|
|
152
|
+
if hook_trigger_reason == "speechDetected":
|
|
153
|
+
transcript = TranscriptResult.from_speech_result(message)
|
|
154
|
+
await handle_gather_completed(transcript, on_new_message, ws)
|
|
155
|
+
elif hook_trigger_reason == "timeout":
|
|
156
|
+
gather_timeout = GatherTimeout.from_message(message)
|
|
157
|
+
await handle_gather_timeout(gather_timeout, ws)
|
|
158
|
+
elif hook_trigger_reason == "dtmfDetected":
|
|
159
|
+
# for now, let's handle it as normal user input with a
|
|
160
|
+
# confidence of 1.0
|
|
161
|
+
transcript = TranscriptResult.from_dtmf_result(message)
|
|
162
|
+
await handle_gather_completed(transcript, on_new_message, ws)
|
|
163
|
+
else:
|
|
164
|
+
structlogger.debug(
|
|
165
|
+
"jambonz.websocket.message.verb_hook",
|
|
166
|
+
call_sid=message.get("call_sid"),
|
|
167
|
+
reason=hook_trigger_reason,
|
|
168
|
+
message=message,
|
|
169
|
+
)
|
|
170
|
+
elif message.get("type") == "verb:status":
|
|
171
|
+
verb_status = VerbStatusChanged.from_message(message)
|
|
172
|
+
await handle_verb_status(verb_status)
|
|
173
|
+
elif message.get("type") == "jambonz:error":
|
|
174
|
+
# jambonz ran into a fatal error handling the call. the call will be
|
|
175
|
+
# terminated.
|
|
176
|
+
structlogger.error("jambonz.websocket.message.error", message=message)
|
|
177
|
+
else:
|
|
178
|
+
structlogger.warning("jambonz.websocket.message.unknown_type", message=message)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def handle_new_session(
|
|
182
|
+
message: NewSessionMessage,
|
|
183
|
+
on_new_message: Callable[[UserMessage], Awaitable[Any]],
|
|
184
|
+
ws: Websocket,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Handle new session message."""
|
|
187
|
+
from rasa.core.channels.voice_aware.jambonz import JambonzWebsocketOutput
|
|
188
|
+
|
|
189
|
+
structlogger.debug("jambonz.websocket.message.new_call", call_sid=message.call_sid)
|
|
190
|
+
output_channel = JambonzWebsocketOutput(ws, message.call_sid)
|
|
191
|
+
user_msg = UserMessage(
|
|
192
|
+
text="/session_start",
|
|
193
|
+
output_channel=output_channel,
|
|
194
|
+
sender_id=message.call_sid,
|
|
195
|
+
metadata={},
|
|
196
|
+
)
|
|
197
|
+
await send_config_ack(message.message_id, ws)
|
|
198
|
+
await on_new_message(user_msg)
|
|
199
|
+
await send_gather_input(ws)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def handle_gather_completed(
|
|
203
|
+
transcript_result: TranscriptResult,
|
|
204
|
+
on_new_message: Callable[[UserMessage], Awaitable[Any]],
|
|
205
|
+
ws: Websocket,
|
|
206
|
+
) -> None:
|
|
207
|
+
"""Handle changes to commands we have send to jambonz.
|
|
208
|
+
|
|
209
|
+
This includes results of gather calles with their transcription.
|
|
210
|
+
"""
|
|
211
|
+
from rasa.core.channels.voice_aware.jambonz import JambonzWebsocketOutput
|
|
212
|
+
|
|
213
|
+
if not transcript_result.is_final:
|
|
214
|
+
# in case of a non final transcript, we are going to wait for the final
|
|
215
|
+
# one and ignore the partial one
|
|
216
|
+
structlogger.debug(
|
|
217
|
+
"jambonz.websocket.message.transcript_partial",
|
|
218
|
+
call_sid=transcript_result.call_sid,
|
|
219
|
+
number_of_transcripts=len(transcript_result.transcripts),
|
|
220
|
+
)
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
if transcript_result.transcripts:
|
|
224
|
+
most_likely_transcript = transcript_result.transcripts[0]
|
|
225
|
+
output_channel = JambonzWebsocketOutput(ws, transcript_result.call_sid)
|
|
226
|
+
user_msg = UserMessage(
|
|
227
|
+
text=most_likely_transcript.text,
|
|
228
|
+
output_channel=output_channel,
|
|
229
|
+
sender_id=transcript_result.call_sid,
|
|
230
|
+
metadata={},
|
|
231
|
+
)
|
|
232
|
+
structlogger.debug(
|
|
233
|
+
"jambonz.websocket.message.transcript",
|
|
234
|
+
call_sid=transcript_result.call_sid,
|
|
235
|
+
transcript=most_likely_transcript.text,
|
|
236
|
+
confidence=most_likely_transcript.confidence,
|
|
237
|
+
number_of_transcripts=len(transcript_result.transcripts),
|
|
238
|
+
)
|
|
239
|
+
await on_new_message(user_msg)
|
|
240
|
+
else:
|
|
241
|
+
structlogger.warning(
|
|
242
|
+
"jambonz.websocket.message.no_transcript",
|
|
243
|
+
call_sid=transcript_result.call_sid,
|
|
244
|
+
)
|
|
245
|
+
await send_gather_input(ws)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
async def handle_gather_timeout(gather_timeout: GatherTimeout, ws: Websocket) -> None:
|
|
249
|
+
"""Handle gather timeout."""
|
|
250
|
+
structlogger.debug(
|
|
251
|
+
"jambonz.websocket.message.gather_timeout",
|
|
252
|
+
call_sid=gather_timeout.call_sid,
|
|
253
|
+
)
|
|
254
|
+
# TODO: figure out how to handle timeouts
|
|
255
|
+
await send_ws_text_message(ws, "I'm sorry, I didn't catch that.")
|
|
256
|
+
await send_gather_input(ws)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
async def handle_call_status(call_status: CallStatusChanged) -> None:
|
|
260
|
+
"""Handle changes in the call status."""
|
|
261
|
+
structlogger.debug(
|
|
262
|
+
"jambonz.websocket.message.call_status_changed",
|
|
263
|
+
call_sid=call_status.call_sid,
|
|
264
|
+
message=call_status.status,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
async def handle_session_reconnect(session_reconnect: SessionReconnect) -> None:
|
|
269
|
+
"""Handle session reconnect message."""
|
|
270
|
+
# there is nothing we need to do atm when a session reconnects.
|
|
271
|
+
# this happens if jambonz looses the websocket connection and reconnects
|
|
272
|
+
structlogger.debug(
|
|
273
|
+
"jambonz.websocket.message.session_reconnect",
|
|
274
|
+
call_sid=session_reconnect.call_sid,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
async def handle_verb_status(verb_status: VerbStatusChanged) -> None:
|
|
279
|
+
"""Handle changes in the status of a verb."""
|
|
280
|
+
structlogger.debug(
|
|
281
|
+
"jambonz.websocket.message.verb_status_changed",
|
|
282
|
+
call_sid=verb_status.call_sid,
|
|
283
|
+
event_type=verb_status.event,
|
|
284
|
+
id=verb_status.id,
|
|
285
|
+
name=verb_status.name,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
async def send_config_ack(message_id: str, ws: Websocket) -> None:
|
|
290
|
+
"""Send an ack message to jambonz including the configuration."""
|
|
291
|
+
await ws.send(
|
|
292
|
+
json.dumps(
|
|
293
|
+
{
|
|
294
|
+
"type": "ack",
|
|
295
|
+
"msgid": message_id,
|
|
296
|
+
"data": [{"config": {"notifyEvents": True}}],
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
async def send_gather_input(ws: Websocket) -> None:
|
|
303
|
+
"""Send a gather input command to jambonz."""
|
|
304
|
+
await ws.send(
|
|
305
|
+
json.dumps(
|
|
306
|
+
{
|
|
307
|
+
"type": "command",
|
|
308
|
+
"command": "redirect",
|
|
309
|
+
"queueCommand": True,
|
|
310
|
+
"data": [
|
|
311
|
+
{
|
|
312
|
+
"gather": {
|
|
313
|
+
"input": ["speech", "digits"],
|
|
314
|
+
"minDigits": 1,
|
|
315
|
+
"id": uuid.uuid4().hex,
|
|
316
|
+
"actionHook": "/gather",
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
],
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
async def send_ws_text_message(ws: Websocket, text: Text) -> None:
|
|
326
|
+
"""Send a text message to the websocket using the jambonz interface."""
|
|
327
|
+
await ws.send(
|
|
328
|
+
json.dumps(
|
|
329
|
+
{
|
|
330
|
+
"type": "command",
|
|
331
|
+
"command": "redirect",
|
|
332
|
+
"queueCommand": True,
|
|
333
|
+
"data": [
|
|
334
|
+
{
|
|
335
|
+
"say": {
|
|
336
|
+
# id can be used for status notifications
|
|
337
|
+
"id": uuid.uuid4().hex,
|
|
338
|
+
"text": text,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
],
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
|
|
3
|
+
from rasa.utils.licensing import (
|
|
4
|
+
PRODUCT_AREA,
|
|
5
|
+
VOICE_SCOPE,
|
|
6
|
+
validate_license_from_env,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
structlogger = structlog.get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def validate_voice_license_scope() -> None:
|
|
13
|
+
"""Validate that the correct license scope is present."""
|
|
14
|
+
structlogger.info(
|
|
15
|
+
f"Validating current Rasa Pro license scope which must include "
|
|
16
|
+
f"the '{VOICE_SCOPE}' scope to use the voice channel."
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
voice_product_scope = PRODUCT_AREA + " " + VOICE_SCOPE
|
|
20
|
+
validate_license_from_env(product_area=voice_product_scope)
|
|
File without changes
|
rasa/core/constants.py
CHANGED
|
@@ -61,7 +61,6 @@ SEARCH_POLICY_PRIORITY = CHAT_POLICY_PRIORITY + 1
|
|
|
61
61
|
# flow policy priority
|
|
62
62
|
FLOW_POLICY_PRIORITY = SEARCH_POLICY_PRIORITY + 1
|
|
63
63
|
|
|
64
|
-
|
|
65
64
|
DIALOGUE = "dialogue"
|
|
66
65
|
|
|
67
66
|
# RabbitMQ message property header added to events published using `rasa export`
|
|
@@ -105,3 +104,9 @@ DEFAULT_TEMPLATE_ENGINE = RASA_FORMAT_TEMPLATE_ENGINE
|
|
|
105
104
|
# configuration parameter used to specify the template engine to use
|
|
106
105
|
# for a response
|
|
107
106
|
TEMPLATE_ENGINE_CONFIG_KEY = "template"
|
|
107
|
+
|
|
108
|
+
# metadata keys for bot utterance events
|
|
109
|
+
UTTER_SOURCE_METADATA_KEY = "utter_source"
|
|
110
|
+
DOMAIN_GROUND_TRUTH_METADATA_KEY = "domain_ground_truth"
|
|
111
|
+
ACTIVE_FLOW_METADATA_KEY = "active_flow"
|
|
112
|
+
STEP_ID_METADATA_KEY = "step_id"
|