rasa-pro 3.12.0rc2__py3-none-any.whl → 3.12.0rc3__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/cli/dialogue_understanding_test.py +5 -8
- rasa/cli/llm_fine_tuning.py +47 -12
- rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
- rasa/core/channels/voice_stream/audiocodes.py +19 -6
- rasa/core/channels/voice_stream/call_state.py +3 -9
- rasa/core/channels/voice_stream/genesys.py +40 -55
- rasa/core/channels/voice_stream/voice_channel.py +61 -39
- rasa/core/tracker_store.py +123 -34
- rasa/dialogue_understanding/commands/set_slot_command.py +1 -0
- rasa/dialogue_understanding/commands/utils.py +1 -4
- rasa/dialogue_understanding/generator/command_parser.py +41 -0
- rasa/dialogue_understanding/generator/constants.py +7 -2
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +9 -2
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +29 -48
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_fallback_other_models_template.jinja2 +57 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +23 -50
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +76 -24
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +32 -18
- rasa/dialogue_understanding/processor/command_processor.py +39 -19
- rasa/dialogue_understanding/stack/utils.py +11 -6
- rasa/engine/language.py +67 -25
- rasa/llm_fine_tuning/conversations.py +3 -31
- rasa/llm_fine_tuning/llm_data_preparation_module.py +5 -3
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +18 -13
- rasa/llm_fine_tuning/paraphrasing_module.py +6 -2
- rasa/llm_fine_tuning/train_test_split_module.py +27 -27
- rasa/llm_fine_tuning/utils.py +7 -0
- rasa/shared/constants.py +4 -0
- rasa/shared/core/domain.py +2 -0
- rasa/shared/providers/_configs/azure_entra_id_config.py +8 -8
- rasa/shared/providers/llm/litellm_router_llm_client.py +1 -0
- rasa/shared/providers/router/_base_litellm_router_client.py +38 -7
- rasa/shared/utils/llm.py +69 -13
- rasa/telemetry.py +13 -3
- rasa/tracing/instrumentation/attribute_extractors.py +2 -5
- rasa/validator.py +2 -2
- rasa/version.py +1 -1
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/METADATA +1 -1
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/RECORD +42 -41
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +0 -68
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/entry_points.txt +0 -0
rasa/core/tracker_store.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import itertools
|
|
5
5
|
import json
|
|
6
|
-
import logging
|
|
7
6
|
import os
|
|
8
7
|
from inspect import isawaitable, iscoroutinefunction
|
|
9
8
|
from time import sleep
|
|
@@ -24,6 +23,7 @@ from typing import (
|
|
|
24
23
|
)
|
|
25
24
|
|
|
26
25
|
import sqlalchemy as sa
|
|
26
|
+
import structlog
|
|
27
27
|
from boto3.dynamodb.conditions import Key
|
|
28
28
|
from pymongo.collection import Collection
|
|
29
29
|
|
|
@@ -31,6 +31,7 @@ import rasa.shared.utils.cli
|
|
|
31
31
|
import rasa.shared.utils.common
|
|
32
32
|
import rasa.shared.utils.io
|
|
33
33
|
import rasa.utils.json_utils
|
|
34
|
+
from rasa.constants import DEFAULT_SANIC_WORKERS, ENV_SANIC_WORKERS
|
|
34
35
|
from rasa.core.brokers.broker import EventBroker
|
|
35
36
|
from rasa.core.constants import (
|
|
36
37
|
POSTGRESQL_MAX_OVERFLOW,
|
|
@@ -59,7 +60,7 @@ if TYPE_CHECKING:
|
|
|
59
60
|
from sqlalchemy.engine.url import URL
|
|
60
61
|
from sqlalchemy.orm import Query, Session
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
structlogger = structlog.get_logger(__name__)
|
|
63
64
|
|
|
64
65
|
# default values of PostgreSQL pool size and max overflow
|
|
65
66
|
POSTGRESQL_DEFAULT_MAX_OVERFLOW = 100
|
|
@@ -315,7 +316,10 @@ class TrackerStore:
|
|
|
315
316
|
async def stream_events(self, tracker: DialogueStateTracker) -> None:
|
|
316
317
|
"""Streams events to a message broker."""
|
|
317
318
|
if self.event_broker is None:
|
|
318
|
-
|
|
319
|
+
structlogger.debug(
|
|
320
|
+
"tracker_store.stream_events.no_broker_configured",
|
|
321
|
+
event_info="No event broker configured. Skipping streaming events.",
|
|
322
|
+
)
|
|
319
323
|
return None
|
|
320
324
|
|
|
321
325
|
old_tracker = await self.retrieve(tracker.sender_id)
|
|
@@ -437,13 +441,22 @@ class InMemoryTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
437
441
|
fetch_all_sessions: Whether to fetch all sessions or only the last one.
|
|
438
442
|
"""
|
|
439
443
|
if sender_id not in self.store:
|
|
440
|
-
|
|
444
|
+
structlogger.debug(
|
|
445
|
+
"in_memory_tracker_store.retrieve.no_tracker_for_sender_id",
|
|
446
|
+
event_info=f"Could not find tracker for conversation ID '{sender_id}'.",
|
|
447
|
+
)
|
|
441
448
|
return None
|
|
442
449
|
|
|
443
450
|
tracker = self.deserialise_tracker(sender_id, self.store[sender_id])
|
|
444
451
|
|
|
445
452
|
if not tracker:
|
|
446
|
-
|
|
453
|
+
structlogger.debug(
|
|
454
|
+
"in_memory_tracker_store.retrieve.failed_to_deserialize_tracker",
|
|
455
|
+
event_info=(
|
|
456
|
+
f"Could not deserialize tracker "
|
|
457
|
+
f"for conversation ID '{sender_id}'.",
|
|
458
|
+
),
|
|
459
|
+
)
|
|
447
460
|
return None
|
|
448
461
|
|
|
449
462
|
if fetch_all_sessions:
|
|
@@ -499,7 +512,10 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
499
512
|
|
|
500
513
|
self.key_prefix = DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
|
|
501
514
|
if key_prefix:
|
|
502
|
-
|
|
515
|
+
structlogger.debug(
|
|
516
|
+
"redis_tracker_store.init.custom_key_prefix",
|
|
517
|
+
event_info=f"Setting non-default redis key prefix: '{key_prefix}'.",
|
|
518
|
+
)
|
|
503
519
|
self._set_key_prefix(key_prefix)
|
|
504
520
|
|
|
505
521
|
super().__init__(domain, event_broker, **kwargs)
|
|
@@ -508,9 +524,13 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
508
524
|
if isinstance(key_prefix, str) and key_prefix.isalnum():
|
|
509
525
|
self.key_prefix = key_prefix + ":" + DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
|
|
510
526
|
else:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
527
|
+
structlogger.warning(
|
|
528
|
+
"redis_tracker_store.init.invalid_key_prefix",
|
|
529
|
+
event_info=(
|
|
530
|
+
f"Omitting provided non-alphanumeric "
|
|
531
|
+
f"redis key prefix: '{key_prefix}'. "
|
|
532
|
+
f"Using default '{self.key_prefix}' instead."
|
|
533
|
+
),
|
|
514
534
|
)
|
|
515
535
|
|
|
516
536
|
def _get_key_prefix(self) -> Text:
|
|
@@ -576,7 +596,10 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
576
596
|
"""
|
|
577
597
|
stored = self.red.get(self.key_prefix + sender_id)
|
|
578
598
|
if stored is None:
|
|
579
|
-
|
|
599
|
+
structlogger.debug(
|
|
600
|
+
"redis_tracker_store.retrieve.no_tracker_for_sender_id",
|
|
601
|
+
event_info=f"Could not find tracker for conversation ID '{sender_id}'.",
|
|
602
|
+
)
|
|
580
603
|
return None
|
|
581
604
|
|
|
582
605
|
tracker = self.deserialise_tracker(sender_id, stored)
|
|
@@ -674,6 +697,31 @@ class DynamoTrackerStore(TrackerStore, SerializedTrackerAsDict):
|
|
|
674
697
|
try:
|
|
675
698
|
self.client.describe_table(TableName=table_name)
|
|
676
699
|
except self.client.exceptions.ResourceNotFoundException:
|
|
700
|
+
sanic_workers_count = int(
|
|
701
|
+
os.environ.get(ENV_SANIC_WORKERS, DEFAULT_SANIC_WORKERS)
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
if sanic_workers_count > 1:
|
|
705
|
+
structlogger.error(
|
|
706
|
+
"dynamo_tracker_store.table_creation_not_supported_in_multi_worker_mode",
|
|
707
|
+
event_info=(
|
|
708
|
+
"DynamoDB table creation is not "
|
|
709
|
+
"supported in multi-worker mode. "
|
|
710
|
+
"Table should already exist.",
|
|
711
|
+
),
|
|
712
|
+
)
|
|
713
|
+
raise RasaException(
|
|
714
|
+
"DynamoDB table creation is not supported in "
|
|
715
|
+
"case of multiple sanic workers. To create the table either "
|
|
716
|
+
"run Rasa with a single worker or create the table manually."
|
|
717
|
+
"Here are the defaults which can be used to "
|
|
718
|
+
"create the table manually: "
|
|
719
|
+
f"Table name: {table_name}, Primary key: sender_id, "
|
|
720
|
+
f"key type `HASH`, attribute type `S` (String), "
|
|
721
|
+
"Provisioned throughput: Read capacity units: 5, "
|
|
722
|
+
"Write capacity units: 5"
|
|
723
|
+
)
|
|
724
|
+
|
|
677
725
|
table = dynamo.create_table(
|
|
678
726
|
TableName=self.table_name,
|
|
679
727
|
KeySchema=[{"AttributeName": "sender_id", "KeyType": "HASH"}],
|
|
@@ -1001,7 +1049,10 @@ def create_engine_kwargs(url: Union[Text, "URL"]) -> Dict[Text, Any]:
|
|
|
1001
1049
|
schema_name = os.environ.get(POSTGRESQL_SCHEMA)
|
|
1002
1050
|
|
|
1003
1051
|
if schema_name:
|
|
1004
|
-
|
|
1052
|
+
structlogger.debug(
|
|
1053
|
+
"postgresql_tracker_store.schema_name",
|
|
1054
|
+
event_inf=f"Using PostgreSQL schema '{schema_name}'.",
|
|
1055
|
+
)
|
|
1005
1056
|
kwargs["connect_args"] = {"options": f"-csearch_path={schema_name}"}
|
|
1006
1057
|
|
|
1007
1058
|
# pool_size and max_overflow can be set to control the number of
|
|
@@ -1114,7 +1165,10 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1114
1165
|
|
|
1115
1166
|
self.engine = sa.create_engine(engine_url, **create_engine_kwargs(engine_url))
|
|
1116
1167
|
|
|
1117
|
-
|
|
1168
|
+
structlogger.debug(
|
|
1169
|
+
"sql_tracker_store.connect_to_sql_database",
|
|
1170
|
+
event_info=f"Attempting to connect to database via '{self.engine.url!r}'.",
|
|
1171
|
+
)
|
|
1118
1172
|
|
|
1119
1173
|
# Database might take a while to come up
|
|
1120
1174
|
while True:
|
|
@@ -1133,7 +1187,11 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1133
1187
|
# Several Rasa services started in parallel may attempt to
|
|
1134
1188
|
# create tables at the same time. That is okay so long as
|
|
1135
1189
|
# the first services finishes the table creation.
|
|
1136
|
-
|
|
1190
|
+
structlogger.error(
|
|
1191
|
+
"sql_tracker_store.create_tables_failed",
|
|
1192
|
+
event_info="Could not create tables",
|
|
1193
|
+
exec_info=e,
|
|
1194
|
+
)
|
|
1137
1195
|
|
|
1138
1196
|
self.sessionmaker = sa.orm.session.sessionmaker(bind=self.engine)
|
|
1139
1197
|
break
|
|
@@ -1141,10 +1199,17 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1141
1199
|
sqlalchemy.exc.OperationalError,
|
|
1142
1200
|
sqlalchemy.exc.IntegrityError,
|
|
1143
1201
|
) as error:
|
|
1144
|
-
|
|
1202
|
+
structlogger.warning(
|
|
1203
|
+
"sql_tracker_store.initialisation_error",
|
|
1204
|
+
event_info="Failed to establish a connection to the SQL database. ",
|
|
1205
|
+
exc_info=error,
|
|
1206
|
+
)
|
|
1145
1207
|
sleep(5)
|
|
1146
1208
|
|
|
1147
|
-
|
|
1209
|
+
structlogger.debug(
|
|
1210
|
+
"sql_tracker_store.connected_to_sql_database",
|
|
1211
|
+
event_info=f"Connection to SQL database '{db}' successful.",
|
|
1212
|
+
)
|
|
1148
1213
|
|
|
1149
1214
|
super().__init__(domain, event_broker, **kwargs)
|
|
1150
1215
|
|
|
@@ -1212,7 +1277,7 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1212
1277
|
"""Creates database `db` and updates engine accordingly."""
|
|
1213
1278
|
from sqlalchemy import create_engine
|
|
1214
1279
|
|
|
1215
|
-
if
|
|
1280
|
+
if self.engine.dialect.name != "postgresql":
|
|
1216
1281
|
rasa.shared.utils.io.raise_warning(
|
|
1217
1282
|
"The parameter 'login_db' can only be used with a postgres database."
|
|
1218
1283
|
)
|
|
@@ -1252,7 +1317,11 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1252
1317
|
sqlalchemy.exc.ProgrammingError,
|
|
1253
1318
|
sqlalchemy.exc.IntegrityError,
|
|
1254
1319
|
) as e:
|
|
1255
|
-
|
|
1320
|
+
structlogger.error(
|
|
1321
|
+
"sql_tracker_store.create_database_failed",
|
|
1322
|
+
event_info=f"Could not create database '{database_name}'",
|
|
1323
|
+
exec_info=e,
|
|
1324
|
+
)
|
|
1256
1325
|
|
|
1257
1326
|
@contextlib.contextmanager
|
|
1258
1327
|
def session_scope(self) -> Generator["Session", None, None]:
|
|
@@ -1316,15 +1385,21 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1316
1385
|
events = [json.loads(event.data) for event in serialised_events]
|
|
1317
1386
|
|
|
1318
1387
|
if self.domain and len(events) > 0:
|
|
1319
|
-
|
|
1388
|
+
structlogger.debug(
|
|
1389
|
+
"sql_tracker_store.recreating_tracker",
|
|
1390
|
+
event_info=f"Recreating tracker from sender id '{sender_id}'",
|
|
1391
|
+
)
|
|
1320
1392
|
return DialogueStateTracker.from_dict(
|
|
1321
1393
|
sender_id, events, self.domain.slots
|
|
1322
1394
|
)
|
|
1323
1395
|
else:
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1396
|
+
structlogger.debug(
|
|
1397
|
+
"sql_tracker_store._retrieve.no_tracker_for_sender_id",
|
|
1398
|
+
event_info=(
|
|
1399
|
+
f"Can't retrieve tracker matching "
|
|
1400
|
+
f"sender id '{sender_id}' from SQL storage. "
|
|
1401
|
+
f"Returning `None` instead.",
|
|
1402
|
+
),
|
|
1328
1403
|
)
|
|
1329
1404
|
return None
|
|
1330
1405
|
|
|
@@ -1401,7 +1476,12 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1401
1476
|
)
|
|
1402
1477
|
session.commit()
|
|
1403
1478
|
|
|
1404
|
-
|
|
1479
|
+
structlogger.debug(
|
|
1480
|
+
"sql_tracker_store.save_tracker",
|
|
1481
|
+
event_info=(
|
|
1482
|
+
f"Tracker with sender_id " f"'{tracker.sender_id}' stored to database",
|
|
1483
|
+
),
|
|
1484
|
+
)
|
|
1405
1485
|
|
|
1406
1486
|
def _additional_events(
|
|
1407
1487
|
self, session: "Session", tracker: DialogueStateTracker
|
|
@@ -1469,11 +1549,14 @@ class FailSafeTrackerStore(TrackerStore):
|
|
|
1469
1549
|
if self._on_tracker_store_error:
|
|
1470
1550
|
self._on_tracker_store_error(error)
|
|
1471
1551
|
else:
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1552
|
+
structlogger.error(
|
|
1553
|
+
"fail_safe_tracker_store.tracker_store_error",
|
|
1554
|
+
event_info=(
|
|
1555
|
+
f"Error happened when trying to save conversation tracker to "
|
|
1556
|
+
f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
|
|
1557
|
+
f"the '{InMemoryTrackerStore.__name__}'. Please "
|
|
1558
|
+
f"investigate the following error: {error}."
|
|
1559
|
+
),
|
|
1477
1560
|
)
|
|
1478
1561
|
|
|
1479
1562
|
async def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
|
|
@@ -1525,11 +1608,14 @@ class FailSafeTrackerStore(TrackerStore):
|
|
|
1525
1608
|
if self._on_tracker_store_error:
|
|
1526
1609
|
self._on_tracker_store_error(error)
|
|
1527
1610
|
else:
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1611
|
+
structlogger.error(
|
|
1612
|
+
"fail_safe_tracker_store.tracker_store_retrieve_error",
|
|
1613
|
+
event_info=(
|
|
1614
|
+
f"Error happened when trying to retrieve conversation tracker from "
|
|
1615
|
+
f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
|
|
1616
|
+
f"the '{InMemoryTrackerStore.__name__}'."
|
|
1617
|
+
),
|
|
1618
|
+
exec_info=error,
|
|
1533
1619
|
)
|
|
1534
1620
|
|
|
1535
1621
|
|
|
@@ -1574,7 +1660,10 @@ def _create_from_endpoint_config(
|
|
|
1574
1660
|
domain, endpoint_config, event_broker
|
|
1575
1661
|
)
|
|
1576
1662
|
|
|
1577
|
-
|
|
1663
|
+
structlogger.debug(
|
|
1664
|
+
"tracker_store.create_tracker_store_from_endpoint_config",
|
|
1665
|
+
eventi_info=f"Connected to {tracker_store.__class__.__name__}.",
|
|
1666
|
+
)
|
|
1578
1667
|
|
|
1579
1668
|
return tracker_store
|
|
1580
1669
|
|
|
@@ -5,10 +5,7 @@ import structlog
|
|
|
5
5
|
from rasa.dialogue_understanding.patterns.validate_slot import (
|
|
6
6
|
ValidateSlotPatternFlowStackFrame,
|
|
7
7
|
)
|
|
8
|
-
from rasa.shared.constants import
|
|
9
|
-
ACTION_ASK_PREFIX,
|
|
10
|
-
UTTER_ASK_PREFIX,
|
|
11
|
-
)
|
|
8
|
+
from rasa.shared.constants import ACTION_ASK_PREFIX, UTTER_ASK_PREFIX
|
|
12
9
|
from rasa.shared.core.events import Event, SlotSet
|
|
13
10
|
from rasa.shared.core.slots import Slot
|
|
14
11
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
|
+
import sys
|
|
2
3
|
from functools import lru_cache
|
|
3
4
|
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
|
4
5
|
|
|
@@ -78,6 +79,44 @@ def _get_additional_parsing_logic(
|
|
|
78
79
|
return command_to_parsing_fn_mapper.get(command_clz)
|
|
79
80
|
|
|
80
81
|
|
|
82
|
+
def validate_custom_commands(command_classes: List[Type[PromptCommand]]) -> None:
|
|
83
|
+
clz_not_inheriting_from_command_clz = [
|
|
84
|
+
command_clz
|
|
85
|
+
for command_clz in command_classes
|
|
86
|
+
if not issubclass(command_clz, Command)
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
if clz_not_inheriting_from_command_clz:
|
|
90
|
+
structlogger.error(
|
|
91
|
+
"command_parser.validate_custom_commands.invalid_command",
|
|
92
|
+
invalid_commands=clz_not_inheriting_from_command_clz,
|
|
93
|
+
event_info=(
|
|
94
|
+
"The additional command classes must be a subclass of the 'Command' "
|
|
95
|
+
"class. Please refer to the class in "
|
|
96
|
+
"`rasa.dialogue_understanding.commands.command.Command`"
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
clz_not_adhering_to_prompt_command_protocol = [
|
|
102
|
+
command_clz
|
|
103
|
+
for command_clz in command_classes
|
|
104
|
+
if not isinstance(command_clz, PromptCommand)
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
if clz_not_adhering_to_prompt_command_protocol:
|
|
108
|
+
structlogger.error(
|
|
109
|
+
"command_parser.validate_custom_commands.invalid_command",
|
|
110
|
+
invalid_commands=clz_not_adhering_to_prompt_command_protocol,
|
|
111
|
+
event_info=(
|
|
112
|
+
"The additional command classes must adhere to the 'PromptCommand' "
|
|
113
|
+
"protocol. Please refer to the protocol in "
|
|
114
|
+
"`rasa.dialogue_understanding.commands.prompt_command.PromptCommand`"
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
|
|
119
|
+
|
|
81
120
|
def parse_commands(
|
|
82
121
|
actions: Optional[str],
|
|
83
122
|
flows: FlowsList,
|
|
@@ -93,6 +132,8 @@ def parse_commands(
|
|
|
93
132
|
return []
|
|
94
133
|
|
|
95
134
|
commands: List[Command] = []
|
|
135
|
+
validate_custom_commands(additional_commands or [])
|
|
136
|
+
|
|
96
137
|
default_commands = DEFAULT_COMMANDS
|
|
97
138
|
if default_commands_to_remove:
|
|
98
139
|
default_commands = _create_default_commands(default_commands_to_remove)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from rasa.shared.constants import (
|
|
2
|
+
MAX_TOKENS_CONFIG_KEY,
|
|
2
3
|
MODEL_CONFIG_KEY,
|
|
3
4
|
OPENAI_PROVIDER,
|
|
4
5
|
PROVIDER_CONFIG_KEY,
|
|
6
|
+
TEMPERATURE_CONFIG_KEY,
|
|
5
7
|
TIMEOUT_CONFIG_KEY,
|
|
6
8
|
)
|
|
7
9
|
from rasa.shared.utils.llm import (
|
|
@@ -12,8 +14,8 @@ from rasa.shared.utils.llm import (
|
|
|
12
14
|
DEFAULT_LLM_CONFIG = {
|
|
13
15
|
PROVIDER_CONFIG_KEY: OPENAI_PROVIDER,
|
|
14
16
|
MODEL_CONFIG_KEY: DEFAULT_OPENAI_CHAT_MODEL_NAME_ADVANCED,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
TEMPERATURE_CONFIG_KEY: 0.0,
|
|
18
|
+
MAX_TOKENS_CONFIG_KEY: DEFAULT_OPENAI_MAX_GENERATED_TOKENS,
|
|
17
19
|
TIMEOUT_CONFIG_KEY: 7,
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -28,3 +30,6 @@ FLOW_RETRIEVAL_FLOW_THRESHOLD = 20
|
|
|
28
30
|
|
|
29
31
|
COMMAND_PROMPT_FILE_NAME = "command_prompt.jinja2"
|
|
30
32
|
LLM_BASED_COMMAND_GENERATOR_CONFIG_FILE = "config.json"
|
|
33
|
+
|
|
34
|
+
MODEL_NAME_GPT_4O_2024_11_20 = "gpt-4o-2024-11-20"
|
|
35
|
+
MODEL_NAME_CLAUDE_3_5_SONNET_20240620 = "claude-3-5-sonnet-20240620"
|
|
@@ -175,7 +175,7 @@ class LLMBasedCommandGenerator(
|
|
|
175
175
|
"""
|
|
176
176
|
self.perform_llm_health_check(
|
|
177
177
|
self.config.get(LLM_CONFIG_KEY),
|
|
178
|
-
|
|
178
|
+
self.get_default_llm_config(),
|
|
179
179
|
"llm_based_command_generator.train",
|
|
180
180
|
LLMBasedCommandGenerator.__name__,
|
|
181
181
|
)
|
|
@@ -332,7 +332,9 @@ class LLMBasedCommandGenerator(
|
|
|
332
332
|
Raises:
|
|
333
333
|
ProviderClientAPIException: If an error occurs during the LLM API call.
|
|
334
334
|
"""
|
|
335
|
-
llm = llm_factory(
|
|
335
|
+
llm = llm_factory(
|
|
336
|
+
self.config.get(LLM_CONFIG_KEY), self.get_default_llm_config()
|
|
337
|
+
)
|
|
336
338
|
try:
|
|
337
339
|
return await llm.acompletion(prompt)
|
|
338
340
|
except Exception as e:
|
|
@@ -619,3 +621,8 @@ class LLMBasedCommandGenerator(
|
|
|
619
621
|
)
|
|
620
622
|
)
|
|
621
623
|
return prior_commands, filtered_commands
|
|
624
|
+
|
|
625
|
+
@staticmethod
|
|
626
|
+
def get_default_llm_config() -> Dict[str, Any]:
|
|
627
|
+
"""Get the default LLM config for the command generator."""
|
|
628
|
+
return DEFAULT_LLM_CONFIG
|
|
@@ -1,77 +1,58 @@
|
|
|
1
|
+
## Task Description
|
|
1
2
|
Your task is to analyze the current conversation context and generate a list of actions to start new business processes that we call flows, to extract slots, or respond to small talk and knowledge requests.
|
|
2
3
|
|
|
4
|
+
--
|
|
5
|
+
|
|
3
6
|
## Available Actions:
|
|
4
|
-
* `start flow flow_name`: Starting a flow. For example, `start flow transfer_money` or `start flow list_contacts
|
|
5
|
-
* `set slot slot_name slot_value`: Slot setting. For example, `set slot transfer_money_recipient Freddy`. Can be used to correct and change previously set values
|
|
6
|
-
* `cancel flow`: Cancelling the current flow
|
|
7
|
+
* `start flow flow_name`: Starting a flow. For example, `start flow transfer_money` or `start flow list_contacts`.
|
|
8
|
+
* `set slot slot_name slot_value`: Slot setting. For example, `set slot transfer_money_recipient Freddy`. Can be used to correct and change previously set values.
|
|
9
|
+
* `cancel flow`: Cancelling the current flow.
|
|
7
10
|
* `disambiguate flows flow_name1 flow_name2 ... flow_name_n`: Disambiguate which flow should be started when user input is ambiguous by listing the potential flows as options. For example, `disambiguate flows list_contacts add_contact remove_contact ...` if the user just wrote "contacts".
|
|
8
|
-
* `provide info`: Responding to the user's questions by supplying relevant information, such as answering FAQs or explaining services
|
|
11
|
+
* `provide info`: Responding to the user's questions by supplying relevant information, such as answering FAQs or explaining services.
|
|
9
12
|
* `offtopic reply`: Responding to casual or social user messages that are unrelated to any flows, engaging in friendly conversation and addressing off-topic remarks.
|
|
10
|
-
* `hand over`: Handing over to a human, in case the user seems frustrated or explicitly asks to speak to one
|
|
13
|
+
* `hand over`: Handing over to a human, in case the user seems frustrated or explicitly asks to speak to one.
|
|
11
14
|
|
|
15
|
+
--
|
|
12
16
|
|
|
13
17
|
## General Tips
|
|
14
18
|
* Do not fill slots with abstract values or placeholders.
|
|
19
|
+
* For categorical slots try to match the user message with allowed slot values. Use "other" if you cannot match it.
|
|
20
|
+
* Set the boolean slots based on the user response. Map positive responses to `True`, and negative to `False`.
|
|
21
|
+
* Always refer to the slot description to determine what information should be extracted and how it should be formatted.
|
|
22
|
+
* For text slots, extract values exactly as provided by the user unless the slot description specifies otherwise. Preserve formatting and avoid rewording, truncation, or making assumptions.
|
|
15
23
|
* Only use information provided by the user.
|
|
16
24
|
* Use clarification in ambiguous cases.
|
|
17
25
|
* Multiple flows can be started. If a user wants to digress into a second flow, you do not need to cancel the current flow.
|
|
26
|
+
* Do not cancel the flow unless the user explicitly requests it.
|
|
18
27
|
* Strictly adhere to the provided action format.
|
|
19
|
-
* For categorical slots try to match the user message with potential slot values. Use "other" if you cannot match it
|
|
20
28
|
* Focus on the last message and take it one step at a time.
|
|
21
29
|
* Use the previous conversation steps only to aid understanding.
|
|
22
30
|
|
|
31
|
+
--
|
|
23
32
|
|
|
24
|
-
## Available Flows
|
|
25
|
-
Use the following structured
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
{% for flow in available_flows %}<flow>
|
|
29
|
-
<name>{{ flow.name }}</name>
|
|
30
|
-
<description>{{ flow.description }}</description>
|
|
31
|
-
<slots>{% for slot in flow.slots %}
|
|
32
|
-
<slot>
|
|
33
|
-
<name>{{ slot.name }}</name>
|
|
34
|
-
<description>{{ slot.description }}</description>
|
|
35
|
-
<allowed_values>{{ slot.allowed_values }}</allowed_values>
|
|
36
|
-
</slot>{% endfor %}
|
|
37
|
-
</slots>
|
|
38
|
-
</flow>
|
|
39
|
-
{% endfor %}
|
|
40
|
-
</flows>
|
|
33
|
+
## Available Flows and Slots
|
|
34
|
+
Use the following structured data:
|
|
35
|
+
```json
|
|
36
|
+
{"flows":[{% for flow in available_flows %}{"name":"{{ flow.name }}","description":"{{ flow.description }}"{% if flow.slots %},"slots":[{% for slot in flow.slots %}{"name":"{{ slot.name }}"{% if slot.description %},"description":"{{ slot.description }}"{% endif %}{% if slot.allowed_values %},"allowed_values":{{ slot.allowed_values }}{% endif %}}{% if not loop.last %},{% endif %}{% endfor %}]{% endif %}}{% if not loop.last %},{% endif %}{% endfor %}]}
|
|
41
37
|
```
|
|
42
38
|
|
|
39
|
+
--
|
|
40
|
+
|
|
43
41
|
## Current State
|
|
44
|
-
{% if current_flow != None %}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<current_step>
|
|
50
|
-
<requested_slot>{{ current_slot }}</requested_slot>
|
|
51
|
-
<requested_slot_description>{{ current_slot_description }}</requested_slot_description>
|
|
52
|
-
</current_step>
|
|
53
|
-
<slots>
|
|
54
|
-
{% for slot in flow_slots %}<slot>
|
|
55
|
-
<name>{{ slot.name }}</name>
|
|
56
|
-
<value>{{ slot.value }}</value>
|
|
57
|
-
<type>{{ slot.type }}</type>
|
|
58
|
-
<description>{{ slot.description }}</description>{% if slot.allowed_values %}
|
|
59
|
-
<allowed_values>{{ slot.allowed_values }}</allowed_values>{% endif %}
|
|
60
|
-
</slot>
|
|
61
|
-
{% endfor %}
|
|
62
|
-
</slots>
|
|
63
|
-
</current_state>
|
|
64
|
-
```
|
|
65
|
-
{% else %}
|
|
66
|
-
You are currently not inside any flow.
|
|
67
|
-
{% endif %}
|
|
42
|
+
{% if current_flow != None %}Use the following structured data:
|
|
43
|
+
```json
|
|
44
|
+
{"active_flow":"{{ current_flow }}","current_step":{"requested_slot":"{{ current_slot }}","requested_slot_description":"{{ current_slot_description }}"},"slots":[{% for slot in flow_slots %}{"name":"{{ slot.name }}","value":"{{ slot.value }}","type":"{{ slot.type }}"{% if slot.description %},"description":"{{ slot.description }}"{% endif %}{% if slot.allowed_values %},"allowed_values":"{{ slot.allowed_values }}"{% endif %}}{% if not loop.last %},{% endif %}{% endfor %}]}
|
|
45
|
+
```{% else %}
|
|
46
|
+
You are currently not inside any flow.{% endif %}
|
|
68
47
|
|
|
48
|
+
---
|
|
69
49
|
|
|
70
50
|
## Conversation History
|
|
71
51
|
{{ current_conversation }}
|
|
72
52
|
|
|
53
|
+
---
|
|
73
54
|
|
|
74
55
|
## Task
|
|
75
|
-
Create an action list with one action per line in response to the
|
|
56
|
+
Create an action list with one action per line in response to the user's last message: """{{ user_message }}""".
|
|
76
57
|
|
|
77
58
|
Your action list:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
## Task Description
|
|
2
|
+
Your task is to analyze the current conversation context and generate a list of actions to start new business processes that we call flows, to extract slots, or respond to small talk and knowledge requests.
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Available Flows and Slots
|
|
7
|
+
Use the following structured data:
|
|
8
|
+
```json
|
|
9
|
+
{"flows":[{% for flow in available_flows %}{"name":"{{ flow.name }}","description":"{{ flow.description }}"{% if flow.slots %},"slots":[{% for slot in flow.slots %}{"name":"{{ slot.name }}"{% if slot.description %},"description":"{{ slot.description }}"{% endif %}{% if slot.allowed_values %},"allowed_values":{{ slot.allowed_values }}{% endif %}}{% if not loop.last %},{% endif %}{% endfor %}]{% endif %}}{% if not loop.last %},{% endif %}{% endfor %}]}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Available Actions:
|
|
15
|
+
* `start flow flow_name`: Starting a flow. For example, `start flow transfer_money` or `start flow list_contacts`.
|
|
16
|
+
* `set slot slot_name slot_value`: Slot setting. For example, `set slot transfer_money_recipient Freddy`. Can be used to correct and change previously set values.
|
|
17
|
+
* `cancel flow`: Cancelling the current flow.
|
|
18
|
+
* `disambiguate flows flow_name1 flow_name2 ... flow_name_n`: Disambiguate which flow should be started when user input is ambiguous by listing the potential flows as options. For example, `disambiguate flows list_contacts add_contact remove_contact ...` if the user just wrote "contacts".
|
|
19
|
+
* `provide info`: Responding to the user's questions by supplying relevant information, such as answering FAQs or explaining services.
|
|
20
|
+
* `offtopic reply`: Responding to casual or social user messages that are unrelated to any flows, engaging in friendly conversation and addressing off-topic remarks.
|
|
21
|
+
* `hand over`: Handing over to a human, in case the user seems frustrated or explicitly asks to speak to one.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## General Tips
|
|
26
|
+
* Do not fill slots with abstract values or placeholders.
|
|
27
|
+
* For categorical slots try to match the user message with allowed slot values. Use "other" if you cannot match it.
|
|
28
|
+
* Set the boolean slots based on the user response. Map positive responses to `True`, and negative to `False`.
|
|
29
|
+
* Extract text slot values exactly as provided by the user. Avoid assumptions, format changes, or partial extractions.
|
|
30
|
+
* Only use information provided by the user.
|
|
31
|
+
* Use clarification in ambiguous cases.
|
|
32
|
+
* Multiple flows can be started. If a user wants to digress into a second flow, you do not need to cancel the current flow.
|
|
33
|
+
* Do not cancel the flow unless the user explicitly requests it.
|
|
34
|
+
* Strictly adhere to the provided action format.
|
|
35
|
+
* Focus on the last message and take it one step at a time.
|
|
36
|
+
* Use the previous conversation steps only to aid understanding.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Current State
|
|
41
|
+
{% if current_flow != None %}Use the following structured data:
|
|
42
|
+
```json
|
|
43
|
+
{"active_flow":"{{ current_flow }}","current_step":{"requested_slot":"{{ current_slot }}","requested_slot_description":"{{ current_slot_description }}"},"slots":[{% for slot in flow_slots %}{"name":"{{ slot.name }}","value":"{{ slot.value }}","type":"{{ slot.type }}"{% if slot.description %},"description":"{{ slot.description }}"{% endif %}{% if slot.allowed_values %},"allowed_values":"{{ slot.allowed_values }}"{% endif %}}{% if not loop.last %},{% endif %}{% endfor %}]}
|
|
44
|
+
```{% else %}
|
|
45
|
+
You are currently not inside any flow.{% endif %}
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Conversation History
|
|
50
|
+
{{ current_conversation }}
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Task
|
|
55
|
+
Create an action list with one action per line in response to the user's last message: """{{ user_message }}""".
|
|
56
|
+
|
|
57
|
+
Your action list:
|