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.

Files changed (43) hide show
  1. rasa/cli/dialogue_understanding_test.py +5 -8
  2. rasa/cli/llm_fine_tuning.py +47 -12
  3. rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
  4. rasa/core/channels/voice_stream/audiocodes.py +19 -6
  5. rasa/core/channels/voice_stream/call_state.py +3 -9
  6. rasa/core/channels/voice_stream/genesys.py +40 -55
  7. rasa/core/channels/voice_stream/voice_channel.py +61 -39
  8. rasa/core/tracker_store.py +123 -34
  9. rasa/dialogue_understanding/commands/set_slot_command.py +1 -0
  10. rasa/dialogue_understanding/commands/utils.py +1 -4
  11. rasa/dialogue_understanding/generator/command_parser.py +41 -0
  12. rasa/dialogue_understanding/generator/constants.py +7 -2
  13. rasa/dialogue_understanding/generator/llm_based_command_generator.py +9 -2
  14. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +29 -48
  15. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_fallback_other_models_template.jinja2 +57 -0
  16. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +23 -50
  17. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +76 -24
  18. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +32 -18
  19. rasa/dialogue_understanding/processor/command_processor.py +39 -19
  20. rasa/dialogue_understanding/stack/utils.py +11 -6
  21. rasa/engine/language.py +67 -25
  22. rasa/llm_fine_tuning/conversations.py +3 -31
  23. rasa/llm_fine_tuning/llm_data_preparation_module.py +5 -3
  24. rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +18 -13
  25. rasa/llm_fine_tuning/paraphrasing_module.py +6 -2
  26. rasa/llm_fine_tuning/train_test_split_module.py +27 -27
  27. rasa/llm_fine_tuning/utils.py +7 -0
  28. rasa/shared/constants.py +4 -0
  29. rasa/shared/core/domain.py +2 -0
  30. rasa/shared/providers/_configs/azure_entra_id_config.py +8 -8
  31. rasa/shared/providers/llm/litellm_router_llm_client.py +1 -0
  32. rasa/shared/providers/router/_base_litellm_router_client.py +38 -7
  33. rasa/shared/utils/llm.py +69 -13
  34. rasa/telemetry.py +13 -3
  35. rasa/tracing/instrumentation/attribute_extractors.py +2 -5
  36. rasa/validator.py +2 -2
  37. rasa/version.py +1 -1
  38. {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/METADATA +1 -1
  39. {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/RECORD +42 -41
  40. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +0 -68
  41. {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/NOTICE +0 -0
  42. {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/WHEEL +0 -0
  43. {rasa_pro-3.12.0rc2.dist-info → rasa_pro-3.12.0rc3.dist-info}/entry_points.txt +0 -0
@@ -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
- logger = logging.getLogger(__name__)
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
- logger.debug("No event broker configured. Skipping streaming events.")
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
- logger.debug(f"Could not find tracker for conversation ID '{sender_id}'.")
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
- logger.debug(f"Could not find tracker for conversation ID '{sender_id}'.")
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
- logger.debug(f"Setting non-default redis key prefix: '{key_prefix}'.")
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
- logger.warning(
512
- f"Omitting provided non-alphanumeric redis key prefix: '{key_prefix}'. "
513
- f"Using default '{self.key_prefix}' instead."
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
- logger.debug(f"Could not find tracker for conversation ID '{sender_id}'.")
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
- logger.debug(f"Using PostgreSQL schema '{schema_name}'.")
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
- logger.debug(f"Attempting to connect to database via '{self.engine.url!r}'.")
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
- logger.error(f"Could not create tables: {e}")
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
- logger.warning(error)
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
- logger.debug(f"Connection to SQL database '{db}' successful.")
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 not self.engine.dialect.name == "postgresql":
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
- logger.error(f"Could not create database '{database_name}': {e}")
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
- logger.debug(f"Recreating tracker from sender id '{sender_id}'")
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
- logger.debug(
1325
- f"Can't retrieve tracker matching "
1326
- f"sender id '{sender_id}' from SQL storage. "
1327
- f"Returning `None` instead."
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
- logger.debug(f"Tracker with sender_id '{tracker.sender_id}' stored to database")
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
- logger.error(
1473
- f"Error happened when trying to save conversation tracker to "
1474
- f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
1475
- f"the '{InMemoryTrackerStore.__name__}'. Please "
1476
- f"investigate the following error: {error}."
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
- logger.error(
1529
- f"Error happened when trying to retrieve conversation tracker from "
1530
- f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
1531
- f"the '{InMemoryTrackerStore.__name__}'. Please "
1532
- f"investigate the following error: {error}."
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
- logger.debug(f"Connected to {tracker_store.__class__.__name__}.")
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
 
@@ -126,6 +126,7 @@ class SetSlotCommand(Command):
126
126
  if (
127
127
  self.name not in slots_of_active_flow
128
128
  and self.name != ROUTE_TO_CALM_SLOT
129
+ and not slot.is_builtin
129
130
  and self.extractor
130
131
  in {
131
132
  SetSlotExtractor.LLM.value,
@@ -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
- "temperature": 0.0,
16
- "max_tokens": DEFAULT_OPENAI_MAX_GENERATED_TOKENS,
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
- DEFAULT_LLM_CONFIG,
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(self.config.get(LLM_CONFIG_KEY), DEFAULT_LLM_CONFIG)
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 date:
26
- ```xml
27
- <flows>
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
- Use the following structured date:
46
- ```xml
47
- <current_state>
48
- <active_flow>{{ current_flow }}</active_flow>
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 users last message: """{{ user_message }}""".
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: