rasa-pro 3.13.0.dev7__py3-none-any.whl → 3.13.0.dev9__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 (215) hide show
  1. rasa/__main__.py +0 -3
  2. rasa/api.py +1 -1
  3. rasa/cli/dialogue_understanding_test.py +1 -1
  4. rasa/cli/e2e_test.py +1 -1
  5. rasa/cli/evaluate.py +1 -1
  6. rasa/cli/export.py +1 -1
  7. rasa/cli/llm_fine_tuning.py +12 -11
  8. rasa/cli/project_templates/defaults.py +133 -0
  9. rasa/cli/run.py +1 -1
  10. rasa/cli/studio/link.py +53 -0
  11. rasa/cli/studio/pull.py +78 -0
  12. rasa/cli/studio/push.py +78 -0
  13. rasa/cli/studio/studio.py +12 -0
  14. rasa/cli/studio/upload.py +8 -0
  15. rasa/cli/train.py +1 -1
  16. rasa/cli/utils.py +1 -1
  17. rasa/cli/x.py +1 -1
  18. rasa/constants.py +2 -0
  19. rasa/core/__init__.py +0 -16
  20. rasa/core/actions/action.py +5 -1
  21. rasa/core/actions/action_repeat_bot_messages.py +18 -22
  22. rasa/core/actions/action_run_slot_rejections.py +0 -1
  23. rasa/core/agent.py +16 -1
  24. rasa/core/available_endpoints.py +146 -0
  25. rasa/core/brokers/pika.py +1 -2
  26. rasa/core/channels/botframework.py +2 -2
  27. rasa/core/channels/channel.py +2 -2
  28. rasa/core/channels/development_inspector.py +1 -1
  29. rasa/core/channels/facebook.py +1 -4
  30. rasa/core/channels/hangouts.py +8 -5
  31. rasa/core/channels/inspector/README.md +3 -3
  32. rasa/core/channels/inspector/dist/assets/{arc-c4b064fc.js → arc-02053cc1.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-215b5026.js → blockDiagram-38ab4fdb-008b6289.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-2b54a0a3.js → c4Diagram-3d4e48cf-fb2597be.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/channel-078dada8.js +1 -0
  36. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-daacea5f.js → classDiagram-70f12bd4-7f847e00.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-930d4dc2.js → classDiagram-v2-f2320105-ba1d689b.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/clone-5b4516de.js +1 -0
  39. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-83c206ba.js → createText-2e5e7dd3-dd8e67c4.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-b0eb01d0.js → edges-e0da2a9e-10784939.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-17586500.js → erDiagram-9861fffd-24947ae6.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-be2a1776.js → flowDb-956e92f1-a9ced505.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-c2120ebd.js → flowDiagram-66a62f08-afda9c7c.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-f9613071.js +1 -0
  45. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-a6ab5c48.js → flowchart-elk-definition-4a651766-6ef530b8.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-ef613457.js → ganttDiagram-c361ad54-0c7dd39a.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-d59185b3.js → gitGraphDiagram-72cf32ee-b57239d6.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{graph-0f155405.js → graph-9ed57cec.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{index-3862675e-d5f1d1b7.js → index-3862675e-233090de.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{index-47737d3a.js → index-72184470.js} +3 -3
  51. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b07d141f.js → infoDiagram-f8f76790-aa116649.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-1936d429.js → journeyDiagram-49397b02-e51877cc.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{layout-dde8d0f3.js → layout-3ca3798c.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{line-0c2c7ee0.js → line-26ee10d3.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{linear-35dd89a4.js → linear-aedded32.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-56192851.js → mindmap-definition-fc14e90a-d8957261.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-fc21ed78.js → pieDiagram-8a3498a8-d771f885.js} +1 -1
  58. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-25e98518.js → quadrantDiagram-120e2f19-09fdf50c.js} +1 -1
  59. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-546ff1f5.js → requirementDiagram-deff3bca-9f0af02e.js} +1 -1
  60. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-02d8b82d.js → sankeyDiagram-04a897e0-84415b37.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3ca5a92e.js → sequenceDiagram-704730f1-8dec4055.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-128ea07c.js → stateDiagram-587899a1-c5431d07.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-95f290af.js → stateDiagram-v2-d93cdb3a-274e77d9.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-4984898a.js → styles-6aaf32cf-e364a1d7.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1bf266ba.js → styles-9a916d00-0dae36f6.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-60521c63.js → styles-c10674c1-c4641675.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-a25b6e12.js → svgDrawCommon-08f97a94-831fe9a1.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-0fc086bf.js → timeline-definition-85554ec2-c3304b3a.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-44ee592e.js → xychartDiagram-e933f94c-da799369.js} +1 -1
  70. rasa/core/channels/inspector/dist/index.html +1 -1
  71. rasa/core/channels/inspector/src/components/RecruitmentPanel.tsx +1 -1
  72. rasa/core/channels/mattermost.py +1 -1
  73. rasa/core/channels/rasa_chat.py +2 -4
  74. rasa/core/channels/rest.py +5 -4
  75. rasa/core/channels/socketio.py +56 -41
  76. rasa/core/channels/studio_chat.py +314 -10
  77. rasa/core/channels/vier_cvg.py +1 -2
  78. rasa/core/channels/voice_ready/audiocodes.py +2 -9
  79. rasa/core/channels/voice_stream/audiocodes.py +8 -5
  80. rasa/core/channels/voice_stream/browser_audio.py +1 -1
  81. rasa/core/channels/voice_stream/genesys.py +2 -2
  82. rasa/core/channels/voice_stream/tts/__init__.py +8 -0
  83. rasa/core/channels/voice_stream/twilio_media_streams.py +10 -5
  84. rasa/core/channels/voice_stream/voice_channel.py +39 -23
  85. rasa/core/http_interpreter.py +3 -7
  86. rasa/core/information_retrieval/faiss.py +18 -11
  87. rasa/core/information_retrieval/ingestion/__init__.py +0 -0
  88. rasa/core/information_retrieval/ingestion/faq_parser.py +158 -0
  89. rasa/core/jobs.py +2 -1
  90. rasa/core/nlg/contextual_response_rephraser.py +44 -10
  91. rasa/core/nlg/generator.py +0 -1
  92. rasa/core/nlg/interpolator.py +2 -3
  93. rasa/core/nlg/summarize.py +39 -5
  94. rasa/core/policies/enterprise_search_policy.py +262 -62
  95. rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2 +63 -0
  96. rasa/core/policies/flow_policy.py +1 -1
  97. rasa/core/policies/flows/flow_executor.py +96 -17
  98. rasa/core/policies/intentless_policy.py +56 -17
  99. rasa/core/processor.py +104 -51
  100. rasa/core/run.py +33 -11
  101. rasa/core/tracker_stores/tracker_store.py +1 -1
  102. rasa/core/training/interactive.py +1 -1
  103. rasa/core/utils.py +24 -97
  104. rasa/dialogue_understanding/coexistence/intent_based_router.py +2 -1
  105. rasa/dialogue_understanding/coexistence/llm_based_router.py +9 -6
  106. rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -0
  107. rasa/dialogue_understanding/commands/cancel_flow_command.py +5 -1
  108. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -0
  109. rasa/dialogue_understanding/commands/clarify_command.py +5 -1
  110. rasa/dialogue_understanding/commands/command_syntax_manager.py +1 -0
  111. rasa/dialogue_understanding/commands/correct_slots_command.py +1 -3
  112. rasa/dialogue_understanding/commands/human_handoff_command.py +2 -0
  113. rasa/dialogue_understanding/commands/knowledge_answer_command.py +4 -2
  114. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -0
  115. rasa/dialogue_understanding/commands/set_slot_command.py +11 -1
  116. rasa/dialogue_understanding/commands/skip_question_command.py +2 -0
  117. rasa/dialogue_understanding/commands/start_flow_command.py +4 -0
  118. rasa/dialogue_understanding/commands/utils.py +26 -2
  119. rasa/dialogue_understanding/generator/__init__.py +7 -1
  120. rasa/dialogue_understanding/generator/command_generator.py +4 -2
  121. rasa/dialogue_understanding/generator/command_parser.py +2 -2
  122. rasa/dialogue_understanding/generator/command_parser_validator.py +63 -0
  123. rasa/dialogue_understanding/generator/nlu_command_adapter.py +2 -2
  124. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +12 -33
  125. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +78 -0
  126. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +26 -461
  127. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +147 -0
  128. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +477 -0
  129. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -64
  130. rasa/dialogue_understanding/patterns/cancel.py +1 -2
  131. rasa/dialogue_understanding/patterns/clarify.py +1 -1
  132. rasa/dialogue_understanding/patterns/correction.py +2 -2
  133. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +37 -25
  134. rasa/dialogue_understanding/patterns/domain_for_patterns.py +190 -0
  135. rasa/dialogue_understanding/processor/command_processor.py +6 -7
  136. rasa/dialogue_understanding/processor/command_processor_component.py +3 -3
  137. rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +17 -4
  138. rasa/dialogue_understanding/stack/utils.py +3 -1
  139. rasa/dialogue_understanding/utils.py +68 -12
  140. rasa/dialogue_understanding_test/du_test_case.py +1 -1
  141. rasa/dialogue_understanding_test/du_test_runner.py +4 -22
  142. rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +2 -6
  143. rasa/e2e_test/e2e_test_runner.py +1 -1
  144. rasa/engine/constants.py +1 -1
  145. rasa/engine/graph.py +2 -2
  146. rasa/engine/recipes/default_recipe.py +26 -2
  147. rasa/engine/validation.py +3 -2
  148. rasa/hooks.py +0 -28
  149. rasa/llm_fine_tuning/annotation_module.py +39 -9
  150. rasa/llm_fine_tuning/conversations.py +3 -0
  151. rasa/llm_fine_tuning/llm_data_preparation_module.py +66 -49
  152. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +1 -5
  153. rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +52 -44
  154. rasa/llm_fine_tuning/paraphrasing_module.py +10 -12
  155. rasa/llm_fine_tuning/storage.py +4 -4
  156. rasa/llm_fine_tuning/utils.py +63 -1
  157. rasa/model_manager/model_api.py +88 -0
  158. rasa/model_manager/trainer_service.py +4 -4
  159. rasa/plugin.py +1 -11
  160. rasa/privacy/__init__.py +0 -0
  161. rasa/privacy/constants.py +83 -0
  162. rasa/privacy/event_broker_utils.py +77 -0
  163. rasa/privacy/privacy_config.py +281 -0
  164. rasa/privacy/privacy_config_schema.json +86 -0
  165. rasa/privacy/privacy_filter.py +340 -0
  166. rasa/privacy/privacy_manager.py +576 -0
  167. rasa/server.py +23 -2
  168. rasa/shared/constants.py +14 -0
  169. rasa/shared/core/command_payload_reader.py +1 -5
  170. rasa/shared/core/constants.py +4 -3
  171. rasa/shared/core/domain.py +7 -0
  172. rasa/shared/core/events.py +38 -10
  173. rasa/shared/core/flows/flow.py +1 -2
  174. rasa/shared/core/flows/flows_yaml_schema.json +3 -0
  175. rasa/shared/core/flows/steps/collect.py +46 -2
  176. rasa/shared/core/flows/validation.py +16 -3
  177. rasa/shared/core/slots.py +28 -0
  178. rasa/shared/core/training_data/story_reader/yaml_story_reader.py +1 -4
  179. rasa/shared/exceptions.py +4 -0
  180. rasa/shared/utils/common.py +1 -1
  181. rasa/shared/utils/llm.py +191 -6
  182. rasa/shared/utils/yaml.py +32 -0
  183. rasa/studio/data_handler.py +3 -3
  184. rasa/studio/download/download.py +37 -60
  185. rasa/studio/download/flows.py +23 -31
  186. rasa/studio/link.py +200 -0
  187. rasa/studio/pull.py +94 -0
  188. rasa/studio/push.py +131 -0
  189. rasa/studio/upload.py +117 -67
  190. rasa/telemetry.py +82 -25
  191. rasa/tracing/config.py +3 -4
  192. rasa/tracing/constants.py +19 -1
  193. rasa/tracing/instrumentation/attribute_extractors.py +10 -2
  194. rasa/tracing/instrumentation/instrumentation.py +53 -2
  195. rasa/tracing/instrumentation/metrics.py +98 -15
  196. rasa/tracing/metric_instrument_provider.py +75 -3
  197. rasa/utils/common.py +1 -27
  198. rasa/utils/log_utils.py +1 -45
  199. rasa/validator.py +2 -8
  200. rasa/version.py +1 -1
  201. {rasa_pro-3.13.0.dev7.dist-info → rasa_pro-3.13.0.dev9.dist-info}/METADATA +7 -8
  202. {rasa_pro-3.13.0.dev7.dist-info → rasa_pro-3.13.0.dev9.dist-info}/RECORD +205 -189
  203. rasa/anonymization/__init__.py +0 -2
  204. rasa/anonymization/anonymisation_rule_yaml_reader.py +0 -91
  205. rasa/anonymization/anonymization_pipeline.py +0 -286
  206. rasa/anonymization/anonymization_rule_executor.py +0 -266
  207. rasa/anonymization/anonymization_rule_orchestrator.py +0 -119
  208. rasa/anonymization/schemas/config.yml +0 -47
  209. rasa/anonymization/utils.py +0 -118
  210. rasa/core/channels/inspector/dist/assets/channel-3730f5fd.js +0 -1
  211. rasa/core/channels/inspector/dist/assets/clone-e847561e.js +0 -1
  212. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-efbbfe00.js +0 -1
  213. {rasa_pro-3.13.0.dev7.dist-info → rasa_pro-3.13.0.dev9.dist-info}/NOTICE +0 -0
  214. {rasa_pro-3.13.0.dev7.dist-info → rasa_pro-3.13.0.dev9.dist-info}/WHEEL +0 -0
  215. {rasa_pro-3.13.0.dev7.dist-info → rasa_pro-3.13.0.dev9.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,9 @@
1
1
  from contextlib import contextmanager
2
2
  from typing import Any, Dict, Generator, List, Optional, Text
3
3
 
4
- from rasa.dialogue_understanding.commands import Command
4
+ import structlog
5
+
6
+ from rasa.dialogue_understanding.commands import Command, NoopCommand, SetSlotCommand
5
7
  from rasa.dialogue_understanding.constants import (
6
8
  RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME,
7
9
  )
@@ -16,7 +18,6 @@ from rasa.shared.nlu.constants import (
16
18
  KEY_USER_PROMPT,
17
19
  PREDICTED_COMMANDS,
18
20
  PROMPTS,
19
- SET_SLOT_COMMAND,
20
21
  )
21
22
  from rasa.shared.nlu.training_data.message import Message
22
23
  from rasa.shared.providers.llm.llm_response import LLMResponse
@@ -26,6 +27,8 @@ record_commands_and_prompts = get_bool_env_variable(
26
27
  RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME, False
27
28
  )
28
29
 
30
+ structlogger = structlog.get_logger()
31
+
29
32
 
30
33
  @contextmanager
31
34
  def set_record_commands_and_prompts() -> Generator:
@@ -144,21 +147,74 @@ def _handle_via_nlu_in_coexistence(
144
147
  if not tracker:
145
148
  return False
146
149
 
150
+ commands = message.get(COMMANDS, [])
151
+
152
+ # If coexistence routing slot is not active, this setup doesn't
153
+ # support dual routing -> default to CALM
147
154
  if not tracker.has_coexistence_routing_slot:
155
+ structlogger.debug(
156
+ "utils.handle_via_nlu_in_coexistence"
157
+ ".tracker_missing_route_session_to_calm_slot",
158
+ event_info=(
159
+ f"Tracker doesn't have the '{ROUTE_TO_CALM_SLOT}' slot."
160
+ f"Routing to CALM."
161
+ ),
162
+ route_session_to_calm=commands,
163
+ )
148
164
  return False
149
165
 
166
+ # Check if the routing decision is stored in the tracker slot
167
+ # If slot is true -> route to CALM
168
+ # If slot is false -> route to DM1
150
169
  value = tracker.get_slot(ROUTE_TO_CALM_SLOT)
151
170
  if value is not None:
171
+ structlogger.debug(
172
+ "utils.handle_via_nlu_in_coexistence"
173
+ ".tracker_route_session_to_calm_slot_value",
174
+ event_info=(
175
+ f"Tracker slot '{ROUTE_TO_CALM_SLOT}' set to '{value}'. "
176
+ f"Routing to "
177
+ f"{'CALM' if value else 'NLU system'}."
178
+ ),
179
+ route_session_to_calm_value_in_tracker=value,
180
+ )
152
181
  return not value
153
182
 
154
- # routing slot has been reset so we need to check
155
- # the command issued by the Router component
156
- if message.get(COMMANDS):
157
- for command in message.get(COMMANDS):
158
- if (
159
- command.get("command") == SET_SLOT_COMMAND
160
- and command.get("name") == ROUTE_TO_CALM_SLOT
161
- ):
162
- return not command.get("value")
163
-
183
+ # Non-sticky routing to DM1 is only allowed if NoopCommand is the sole predicted
184
+ # command. In that case, route to DM1
185
+ if len(commands) == 1 and commands[0].get("command") == NoopCommand.command():
186
+ structlogger.debug(
187
+ "utils.handle_via_nlu_in_coexistence.noop_command_detected",
188
+ event_info="NoopCommand found. Routing to NLU system non-sticky.",
189
+ commands=commands,
190
+ )
191
+ return True
192
+
193
+ # If the slot was reset (e.g. new session), try to infer routing from
194
+ # attached commands. Look for a SetSlotCommand targeting the ROUTE_TO_CALM_SLOT
195
+ for command in message.get(COMMANDS, []):
196
+ # If slot is true -> route to CALM
197
+ # If slot is false -> route to DM1
198
+ if (
199
+ command.get("command") == SetSlotCommand.command()
200
+ and command.get("name") == ROUTE_TO_CALM_SLOT
201
+ ):
202
+ structlogger.debug(
203
+ "utils.handle_via_nlu_in_coexistence.set_slot_command_detected",
204
+ event_info=(
205
+ f"SetSlotCommand setting the '{ROUTE_TO_CALM_SLOT}' to "
206
+ f"'{command.get('value')}'. "
207
+ f"Routing to "
208
+ f"{'CALM' if command.get('value') else 'NLU system'}."
209
+ ),
210
+ commands=commands,
211
+ )
212
+ return not command.get("value")
213
+
214
+ # If no routing info is available -> default to CALM
215
+ structlogger.debug(
216
+ "utils.handle_via_nlu_in_coexistence.no_routing_info_available",
217
+ event_info="No routing info available. Routing to CALM.",
218
+ commands=commands,
219
+ )
164
220
  return False
@@ -3,9 +3,9 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple
3
3
 
4
4
  from pydantic import BaseModel, Field
5
5
 
6
- from rasa.core import IntentlessPolicy
7
6
  from rasa.core.nlg.contextual_response_rephraser import ContextualResponseRephraser
8
7
  from rasa.core.policies.enterprise_search_policy import EnterpriseSearchPolicy
8
+ from rasa.core.policies.intentless_policy import IntentlessPolicy
9
9
  from rasa.dialogue_understanding.commands.prompt_command import PromptCommand
10
10
  from rasa.dialogue_understanding.generator.command_parser import parse_commands
11
11
  from rasa.dialogue_understanding_test.command_comparison import are_command_lists_equal
@@ -5,10 +5,10 @@ from typing import Any, Dict, List, Optional, Text
5
5
  import structlog
6
6
  from tqdm import tqdm
7
7
 
8
+ from rasa.core.available_endpoints import AvailableEndpoints
8
9
  from rasa.core.channels import CollectingOutputChannel, UserMessage
9
10
  from rasa.core.exceptions import AgentNotReady
10
11
  from rasa.core.persistor import StorageType
11
- from rasa.core.utils import AvailableEndpoints
12
12
  from rasa.dialogue_understanding.commands import Command
13
13
  from rasa.dialogue_understanding.utils import set_record_commands_and_prompts
14
14
  from rasa.dialogue_understanding_test.du_test_case import (
@@ -34,6 +34,7 @@ from rasa.e2e_test.e2e_test_runner import E2ETestRunner
34
34
  from rasa.shared.core.events import UserUttered
35
35
  from rasa.shared.core.trackers import DialogueStateTracker
36
36
  from rasa.shared.nlu.constants import PREDICTED_COMMANDS, PROMPTS
37
+ from rasa.shared.utils.llm import create_tracker_for_user_step
37
38
  from rasa.utils.endpoints import EndpointConfig
38
39
 
39
40
  structlogger = structlog.get_logger()
@@ -179,8 +180,9 @@ class DialogueUnderstandingTestRunner:
179
180
  # create and save the tracker at the time just
180
181
  # before the user message was sent
181
182
  step_sender_id = f"{sender_id}_{user_step_index}"
182
- await self._create_tracker_for_user_step(
183
+ await create_tracker_for_user_step(
183
184
  step_sender_id,
185
+ self.agent,
184
186
  test_case_tracker,
185
187
  user_uttered_event_indices[user_step_index],
186
188
  )
@@ -289,26 +291,6 @@ class DialogueUnderstandingTestRunner:
289
291
 
290
292
  return user_uttered_event
291
293
 
292
- async def _create_tracker_for_user_step(
293
- self,
294
- step_sender_id: str,
295
- test_case_tracker: DialogueStateTracker,
296
- index_user_uttered_event: int,
297
- ) -> None:
298
- """Creates a tracker for the user step."""
299
- tracker = test_case_tracker.copy()
300
- # modify the sender id so that the test case tracker is not overwritten
301
- tracker.sender_id = step_sender_id
302
-
303
- if tracker.events:
304
- # get timestamp of the event just before the user uttered event
305
- timestamp = tracker.events[index_user_uttered_event - 1].timestamp
306
- # revert the tracker to the event just before the user uttered event
307
- tracker = tracker.travel_back_in_time(timestamp)
308
-
309
- # store the tracker with the unique sender id
310
- await self.agent.tracker_store.save(tracker)
311
-
312
294
  async def _send_user_message(
313
295
  self,
314
296
  sender_id: str,
@@ -1,4 +1,3 @@
1
- from datetime import datetime
2
1
  from typing import List, Optional
3
2
 
4
3
  import structlog
@@ -24,6 +23,7 @@ from rasa.shared.core.constants import SlotMappingType
24
23
  from rasa.shared.core.events import BotUttered, SlotSet, UserUttered
25
24
  from rasa.shared.core.trackers import DialogueStateTracker
26
25
  from rasa.shared.nlu.constants import COMMANDS, ENTITIES, INTENT
26
+ from rasa.shared.utils.llm import generate_sender_id
27
27
 
28
28
  structlogger = structlog.get_logger()
29
29
 
@@ -52,7 +52,7 @@ class TestCaseTrackerSimulator:
52
52
  self.test_case = test_case
53
53
  self.output_channel = output_channel or CollectingOutputChannel()
54
54
 
55
- self.sender_id = self._generate_sender_id()
55
+ self.sender_id = generate_sender_id(self.test_case.name)
56
56
 
57
57
  async def simulate_test_case(
58
58
  self,
@@ -150,10 +150,6 @@ class TestCaseTrackerSimulator:
150
150
  user_uttered_event_indices=user_uttered_event_indices,
151
151
  )
152
152
 
153
- def _generate_sender_id(self) -> str:
154
- # add timestamp suffix to ensure sender_id is unique
155
- return f"{self.test_case.name}_{datetime.now()}"
156
-
157
153
  @staticmethod
158
154
  async def _get_latest_user_uttered_event_index(
159
155
  tracker: DialogueStateTracker, user_uttered_event_indices: List[int]
@@ -13,11 +13,11 @@ import structlog
13
13
  from tqdm import tqdm
14
14
 
15
15
  import rasa.shared.utils.io
16
+ from rasa.core.available_endpoints import AvailableEndpoints
16
17
  from rasa.core.channels import CollectingOutputChannel, UserMessage
17
18
  from rasa.core.constants import ACTIVE_FLOW_METADATA_KEY, STEP_ID_METADATA_KEY
18
19
  from rasa.core.exceptions import AgentNotReady
19
20
  from rasa.core.persistor import StorageType
20
- from rasa.core.utils import AvailableEndpoints
21
21
  from rasa.dialogue_understanding_test.du_test_case import DialogueUnderstandingTestCase
22
22
  from rasa.e2e_test.constants import TEST_CASE_NAME, TEST_FILE_NAME
23
23
  from rasa.e2e_test.e2e_config import create_llm_judge_config
rasa/engine/constants.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from typing import List, Optional
2
2
 
3
+ from rasa.core.available_endpoints import AvailableEndpoints
3
4
  from rasa.core.channels import UserMessage
4
- from rasa.core.utils import AvailableEndpoints
5
5
  from rasa.shared.core.trackers import DialogueStateTracker
6
6
  from rasa.shared.importers.importer import TrainingDataImporter
7
7
 
rasa/engine/graph.py CHANGED
@@ -500,9 +500,9 @@ class GraphNode:
500
500
  structlogger.warning(
501
501
  "graph.node.input_not_resolved",
502
502
  node_name=self._node_name,
503
- input_name=i,
503
+ input_name=i, # no PII
504
504
  event_info=(
505
- "Node input was not resolved, there is no putput. "
505
+ "Node input was not resolved, there is no output. "
506
506
  "Another component should have provided this as an output."
507
507
  ),
508
508
  )
@@ -396,7 +396,9 @@ class DefaultV1Recipe(Recipe):
396
396
  return preprocessors
397
397
 
398
398
  def _get_needs_from_args(
399
- self, component: Type[GraphComponent], fn_name: str
399
+ self,
400
+ component: Type[GraphComponent],
401
+ fn_name: str,
400
402
  ) -> Dict[str, str]:
401
403
  """Get the needed arguments from the method on the component.
402
404
 
@@ -434,6 +436,7 @@ class DefaultV1Recipe(Recipe):
434
436
  parameters = {
435
437
  name
436
438
  for name, param in sig.parameters.items()
439
+ # only consider parameters which are positional or keyword
437
440
  if param.kind == param.POSITIONAL_OR_KEYWORD
438
441
  }
439
442
 
@@ -752,8 +755,28 @@ class DefaultV1Recipe(Recipe):
752
755
  predict_config, predict_nodes, train_nodes, preprocessors
753
756
  )
754
757
 
758
+ # The `story_graph_provider` is only needed if the intentless policy is used.
759
+ # If it is not used, we can remove it from the nodes as it slows down the
760
+ # loading time if users have a large number of stories.
761
+ if not self._intentless_policy_used(predict_nodes):
762
+ # Removes the `story_graph_provider` from the nodes
763
+ predict_nodes.pop("story_graph_provider", None)
764
+ if "command_processor" in predict_nodes:
765
+ # Removes story_graph from the command processor inputs
766
+ predict_nodes["command_processor"].needs.pop("story_graph", None)
767
+
755
768
  return predict_nodes
756
769
 
770
+ @staticmethod
771
+ def _intentless_policy_used(nodes: Dict[Text, SchemaNode]) -> bool:
772
+ """Checks if the intentless policy is used in the nodes."""
773
+ from rasa.core.policies.intentless_policy import IntentlessPolicy
774
+
775
+ for schema_node in nodes.values():
776
+ if schema_node.matches_type(IntentlessPolicy):
777
+ return True
778
+ return False
779
+
757
780
  def _add_nlu_predict_nodes(
758
781
  self,
759
782
  last_run_node: Text,
@@ -924,7 +947,8 @@ class DefaultV1Recipe(Recipe):
924
947
  predict_nodes["command_processor"] = SchemaNode(
925
948
  **DEFAULT_PREDICT_KWARGS,
926
949
  needs=self._get_needs_from_args(
927
- CommandProcessorComponent, "execute_commands"
950
+ CommandProcessorComponent,
951
+ "execute_commands",
928
952
  ),
929
953
  uses=CommandProcessorComponent,
930
954
  fn="execute_commands",
rasa/engine/validation.py CHANGED
@@ -23,9 +23,10 @@ import structlog
23
23
  import typing_utils
24
24
 
25
25
  import rasa.utils.common
26
- from rasa.core import ContextualResponseRephraser, IntentlessPolicy
26
+ from rasa.core.available_endpoints import AvailableEndpoints
27
+ from rasa.core.nlg.contextual_response_rephraser import ContextualResponseRephraser
28
+ from rasa.core.policies.intentless_policy import IntentlessPolicy
27
29
  from rasa.core.policies.policy import PolicyPrediction
28
- from rasa.core.utils import AvailableEndpoints
29
30
  from rasa.dialogue_understanding.coexistence.constants import (
30
31
  CALM_ENTRY,
31
32
  NLU_ENTRY,
rasa/hooks.py CHANGED
@@ -9,7 +9,6 @@ import pluggy
9
9
  # across the codebase.
10
10
 
11
11
  if TYPE_CHECKING:
12
- from rasa.anonymization.anonymization_pipeline import AnonymizationPipeline
13
12
  from rasa.cli import SubParsersAction
14
13
  from rasa.core.brokers.broker import EventBroker
15
14
  from rasa.core.tracker_stores.tracker_store import TrackerStore
@@ -88,30 +87,3 @@ def create_tracker_store(
88
87
  endpoint_config=endpoint_config, domain=domain, event_broker=event_broker
89
88
  )
90
89
  return endpoint_config
91
-
92
-
93
- @hookimpl # type: ignore[misc]
94
- def init_anonymization_pipeline(endpoints_file: Optional[Text]) -> None:
95
- """Hook implementation for initializing the anonymization pipeline."""
96
- from rasa.anonymization.anonymization_pipeline import load_anonymization_pipeline
97
-
98
- load_anonymization_pipeline(endpoints_file)
99
-
100
-
101
- @hookimpl # type: ignore[misc]
102
- def get_anonymization_pipeline() -> Optional["AnonymizationPipeline"]:
103
- """Hook implementation for getting the anonymization pipeline."""
104
- from rasa.anonymization.anonymization_pipeline import AnonymizationPipelineProvider
105
-
106
- return AnonymizationPipelineProvider().get_anonymization_pipeline()
107
-
108
-
109
- @hookimpl # type: ignore[misc]
110
- def after_server_stop() -> None:
111
- """Hook implementation for stopping the anonymization pipeline."""
112
- from rasa.anonymization.anonymization_pipeline import AnonymizationPipelineProvider
113
-
114
- anon_pipeline = AnonymizationPipelineProvider().get_anonymization_pipeline()
115
-
116
- if anon_pipeline is not None:
117
- anon_pipeline.stop()
@@ -10,7 +10,9 @@ from rasa.e2e_test.e2e_test_runner import TEST_TURNS_TYPE, E2ETestRunner
10
10
  from rasa.llm_fine_tuning.conversations import Conversation, ConversationStep
11
11
  from rasa.llm_fine_tuning.storage import StorageContext
12
12
  from rasa.shared.core.constants import USER
13
+ from rasa.shared.core.events import UserUttered
13
14
  from rasa.shared.core.trackers import DialogueStateTracker
15
+ from rasa.shared.exceptions import FinetuningDataPreparationException
14
16
  from rasa.shared.nlu.constants import LLM_COMMANDS, LLM_PROMPT
15
17
  from rasa.shared.utils.llm import tracker_as_readable_transcript
16
18
 
@@ -37,7 +39,7 @@ def annotate_e2e_tests(
37
39
  storage_context: StorageContext,
38
40
  ) -> List[Conversation]:
39
41
  with set_preparing_fine_tuning_data():
40
- converations = asyncio.run(
42
+ conversations = asyncio.run(
41
43
  e2e_test_runner.run_tests_for_fine_tuning(
42
44
  test_suite.test_cases,
43
45
  test_suite.fixtures,
@@ -46,10 +48,11 @@ def annotate_e2e_tests(
46
48
  )
47
49
 
48
50
  storage_context.write_conversations(
49
- converations, ANNOTATION_MODULE_STORAGE_LOCATION
51
+ conversations,
52
+ ANNOTATION_MODULE_STORAGE_LOCATION,
50
53
  )
51
54
 
52
- return converations
55
+ return conversations
53
56
 
54
57
 
55
58
  def _get_previous_actual_step_output(
@@ -80,25 +83,45 @@ def generate_conversation(
80
83
  Conversation.
81
84
  """
82
85
  steps = []
86
+ tracker_event_indices = [
87
+ i for i, event in enumerate(tracker.events) if isinstance(event, UserUttered)
88
+ ]
89
+
90
+ if len(test_case.steps) != len(tracker_event_indices):
91
+ raise FinetuningDataPreparationException(
92
+ "Number of test case steps and tracker events do not match."
93
+ )
83
94
 
84
95
  if assertions_used:
85
96
  # we only have user steps, extract the bot response from the bot uttered
86
97
  # events of the test turn
87
- for i, original_step in enumerate(test_case.steps):
98
+ for i, (original_step, tracker_event_index) in enumerate(
99
+ zip(test_case.steps, tracker_event_indices)
100
+ ):
88
101
  previous_turn = _get_previous_actual_step_output(test_turns, i)
89
102
  steps.append(
90
103
  _convert_to_conversation_step(
91
- original_step, test_turns[i], test_case.name, previous_turn
104
+ original_step,
105
+ test_turns[i],
106
+ test_case.name,
107
+ previous_turn,
108
+ tracker_event_index,
92
109
  )
93
110
  )
94
111
  steps.extend(_create_bot_test_steps(test_turns[i]))
95
112
  else:
96
- for i, original_step in enumerate(test_case.steps):
113
+ for i, (original_step, tracker_event_index) in enumerate(
114
+ zip(test_case.steps, tracker_event_indices)
115
+ ):
97
116
  if original_step.actor == USER:
98
117
  previous_turn = _get_previous_actual_step_output(test_turns, i)
99
118
  steps.append(
100
119
  _convert_to_conversation_step(
101
- original_step, test_turns[i], test_case.name, previous_turn
120
+ original_step,
121
+ test_turns[i],
122
+ test_case.name,
123
+ previous_turn,
124
+ tracker_event_index,
102
125
  )
103
126
  )
104
127
  else:
@@ -120,7 +143,7 @@ def generate_conversation(
120
143
 
121
144
  transcript = tracker_as_readable_transcript(tracker, max_turns=None)
122
145
 
123
- return Conversation(test_case.name, test_case, steps, transcript)
146
+ return Conversation(test_case.name, test_case, steps, transcript, tracker)
124
147
 
125
148
 
126
149
  def _create_bot_test_steps(current_turn: ActualStepOutput) -> List[TestStep]:
@@ -140,6 +163,7 @@ def _convert_to_conversation_step(
140
163
  current_turn: ActualStepOutput,
141
164
  test_case_name: str,
142
165
  previous_turn: Optional[ActualStepOutput],
166
+ tracker_event_index: Optional[int] = None,
143
167
  ) -> Union[TestStep, ConversationStep]:
144
168
  if not current_step.text == current_turn.text or not isinstance(
145
169
  current_turn, ActualStepOutput
@@ -169,7 +193,13 @@ def _convert_to_conversation_step(
169
193
  commands = [Command.command_from_json(data) for data in llm_commands]
170
194
  rephrase = _should_be_rephrased(current_turn.text, previous_turn, test_case_name)
171
195
 
172
- return ConversationStep(current_step, commands, llm_prompt, rephrase=rephrase)
196
+ return ConversationStep(
197
+ current_step,
198
+ commands,
199
+ llm_prompt,
200
+ rephrase=rephrase,
201
+ tracker_event_index=tracker_event_index,
202
+ )
173
203
 
174
204
 
175
205
  def _should_be_rephrased(
@@ -4,6 +4,7 @@ from typing import Any, Dict, Iterator, List, Optional, Union
4
4
  from rasa.dialogue_understanding.commands.prompt_command import PromptCommand
5
5
  from rasa.e2e_test.e2e_test_case import TestCase, TestStep
6
6
  from rasa.shared.core.constants import USER
7
+ from rasa.shared.core.trackers import DialogueStateTracker
7
8
 
8
9
 
9
10
  @dataclass
@@ -14,6 +15,7 @@ class ConversationStep:
14
15
  failed_rephrasings: List[str] = field(default_factory=list)
15
16
  passed_rephrasings: List[str] = field(default_factory=list)
16
17
  rephrase: bool = True
18
+ tracker_event_index: Optional[int] = None
17
19
 
18
20
  def as_dict(self) -> Dict[str, Any]:
19
21
  data = {
@@ -40,6 +42,7 @@ class Conversation:
40
42
  original_e2e_test_case: TestCase
41
43
  steps: List[Union[TestStep, ConversationStep]]
42
44
  transcript: str
45
+ tracker: Optional[DialogueStateTracker] = None
43
46
 
44
47
  def iterate_over_annotated_user_steps(
45
48
  self, rephrase: Optional[bool] = None
@@ -1,13 +1,23 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Dict, List, Optional
2
+ from typing import Any, Dict, List, Optional, cast
3
3
 
4
4
  import structlog
5
5
  from tqdm import tqdm
6
6
 
7
+ from rasa.core.agent import Agent
8
+ from rasa.core.channels import UserMessage
7
9
  from rasa.dialogue_understanding.commands.prompt_command import PromptCommand
10
+ from rasa.dialogue_understanding.utils import set_record_commands_and_prompts
8
11
  from rasa.llm_fine_tuning.conversations import Conversation, ConversationStep
9
12
  from rasa.llm_fine_tuning.storage import StorageContext
10
- from rasa.llm_fine_tuning.utils import commands_as_string
13
+ from rasa.llm_fine_tuning.utils import (
14
+ commands_as_string,
15
+ make_mock_invoke_llm,
16
+ patch_invoke_llm_in_generators,
17
+ )
18
+ from rasa.shared.core.trackers import DialogueStateTracker
19
+ from rasa.shared.nlu.constants import KEY_USER_PROMPT, PROMPTS
20
+ from rasa.shared.utils.llm import generate_sender_id
11
21
 
12
22
  LLM_DATA_PREPARATION_MODULE_STORAGE_LOCATION = "3_llm_finetune_data/llm_ft_data.jsonl"
13
23
 
@@ -47,40 +57,8 @@ def _create_data_point(
47
57
  )
48
58
 
49
59
 
50
- def _update_prompt(
51
- prompt: str,
52
- original_user_steps: List[ConversationStep],
53
- rephrased_user_steps: List[str],
54
- ) -> Optional[str]:
55
- if len(original_user_steps) != len(rephrased_user_steps):
56
- structlogger.debug(
57
- "llm_fine_tuning.llm_data_preparation_module.failed_to_update_prompt",
58
- original_user_steps=[
59
- step.original_test_step.text for step in original_user_steps
60
- ],
61
- rephrased_user_steps=rephrased_user_steps,
62
- )
63
- return None
64
-
65
- updated_prompt = prompt
66
- for user_step, rephrased_message in zip(original_user_steps, rephrased_user_steps):
67
- # replace all occurrences of the original user message with the rephrased user
68
- # message in the conversation history mentioned in the prompt
69
- updated_prompt = updated_prompt.replace(
70
- f"USER: {user_step.original_test_step.text}", f"USER: {rephrased_message}"
71
- )
72
-
73
- # replace the latest user message mentioned in the prompt
74
- updated_prompt = updated_prompt.replace(
75
- f"'''{original_user_steps[-1].original_test_step.text}'''",
76
- f"'''{rephrased_user_steps[-1]}'''",
77
- )
78
-
79
- return updated_prompt
80
-
81
-
82
- def _convert_conversation_into_llm_data(
83
- conversation: Conversation,
60
+ async def _convert_conversation_into_llm_data(
61
+ conversation: Conversation, agent: Agent
84
62
  ) -> List[LLMDataExample]:
85
63
  data = []
86
64
 
@@ -95,18 +73,52 @@ def _convert_conversation_into_llm_data(
95
73
  # create data point for the original e2e test case
96
74
  data.append(_create_data_point(step.llm_prompt, step, conversation))
97
75
 
98
- # create data points using the rephrasings, e.g. 'new_conversations'
99
- for rephrased_user_steps in new_conversations:
100
- # +1 to include the current user turn
101
- prompt = _update_prompt(
102
- step.llm_prompt,
103
- original_user_steps[: i + 1],
104
- rephrased_user_steps[: i + 1],
76
+ test_case_name = conversation.name
77
+
78
+ # create data points using the rephrasings, e.g. 'new_conversations'
79
+ for rephrased_user_steps in new_conversations:
80
+ sender_id = generate_sender_id(test_case_name)
81
+ # create a new tracker to be able to simulate the conversation from start
82
+ await agent.tracker_store.save(DialogueStateTracker(sender_id, slots=[]))
83
+ # simulate the conversation to get the prompts
84
+ for i, step in enumerate(original_user_steps):
85
+ rephrased_user_message = rephrased_user_steps[i]
86
+ user_message = UserMessage(rephrased_user_message, sender_id=sender_id)
87
+
88
+ expected_commands = "\n".join(
89
+ [command.to_dsl() for command in step.llm_commands]
90
+ )
91
+ fake_invoke_function = make_mock_invoke_llm(expected_commands)
92
+
93
+ with (
94
+ set_record_commands_and_prompts(),
95
+ patch_invoke_llm_in_generators(fake_invoke_function),
96
+ ):
97
+ await agent.handle_message(user_message)
98
+
99
+ rephrased_tracker = await agent.tracker_store.retrieve(sender_id)
100
+ if rephrased_tracker is None:
101
+ # if tracker doesn't exist, we can't create a data point
102
+ continue
103
+
104
+ latest_message = rephrased_tracker.latest_message
105
+ if latest_message is None:
106
+ # if there is no latest message, we don't create a data point
107
+ continue
108
+
109
+ # tell the type checker what we expect to find under "prompts"
110
+ prompts = cast(
111
+ Optional[List[Dict[str, Any]]], latest_message.parse_data.get(PROMPTS)
105
112
  )
106
- if prompt:
113
+
114
+ if prompts:
115
+ # as we only use single step or compact command generator,
116
+ # there is always exactly one prompt
117
+ prompt = prompts[0]
118
+ user_prompt: Optional[str] = prompt.get(KEY_USER_PROMPT)
107
119
  data.append(
108
120
  _create_data_point(
109
- prompt, step, conversation, rephrased_user_steps[i]
121
+ user_prompt, step, conversation, rephrased_user_message
110
122
  )
111
123
  )
112
124
 
@@ -149,7 +161,7 @@ def _construct_new_conversations(conversation: Conversation) -> List[List[str]]:
149
161
  current_conversation.append(step.original_test_step.text)
150
162
  continue
151
163
 
152
- # some user steps might have less rephrasings than others
164
+ # some user steps might have fewer rephrasings than others
153
165
  # loop over the rephrasings
154
166
  index = i % len(step.passed_rephrasings)
155
167
  current_conversation.append(step.passed_rephrasings[index])
@@ -165,13 +177,18 @@ def _construct_new_conversations(conversation: Conversation) -> List[List[str]]:
165
177
  return new_conversations
166
178
 
167
179
 
168
- def convert_to_fine_tuning_data(
169
- conversations: List[Conversation], storage_context: StorageContext
180
+ async def convert_to_fine_tuning_data(
181
+ conversations: List[Conversation],
182
+ storage_context: StorageContext,
183
+ agent: Agent,
170
184
  ) -> List[LLMDataExample]:
171
185
  llm_data = []
172
186
 
173
187
  for i in tqdm(range(len(conversations))):
174
- llm_data.extend(_convert_conversation_into_llm_data(conversations[i]))
188
+ conversation_llm_data = await _convert_conversation_into_llm_data(
189
+ conversations[i], agent
190
+ )
191
+ llm_data.extend(conversation_llm_data)
175
192
 
176
193
  storage_context.write_llm_data(
177
194
  llm_data, LLM_DATA_PREPARATION_MODULE_STORAGE_LOCATION
@@ -22,11 +22,7 @@ from rasa.shared.constants import (
22
22
  from rasa.shared.exceptions import ProviderClientAPIException
23
23
  from rasa.shared.providers.mappings import OPENAI_PROVIDER
24
24
  from rasa.shared.utils.constants import LOG_COMPONENT_SOURCE_METHOD_INIT
25
- from rasa.shared.utils.llm import (
26
- USER,
27
- get_prompt_template,
28
- llm_factory,
29
- )
25
+ from rasa.shared.utils.llm import USER, get_prompt_template, llm_factory
30
26
 
31
27
  SEPARATOR = "\n\n"
32
28
  BACKUP_SEPARATOR = "\nUSER:"