rasa-pro 3.11.0rc2__py3-none-any.whl → 3.11.1__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 (65) hide show
  1. rasa/__main__.py +9 -3
  2. rasa/cli/studio/upload.py +0 -15
  3. rasa/cli/utils.py +1 -1
  4. rasa/core/channels/development_inspector.py +8 -2
  5. rasa/core/channels/voice_ready/audiocodes.py +3 -4
  6. rasa/core/channels/voice_stream/asr/asr_engine.py +19 -1
  7. rasa/core/channels/voice_stream/asr/asr_event.py +1 -1
  8. rasa/core/channels/voice_stream/asr/azure.py +16 -9
  9. rasa/core/channels/voice_stream/asr/deepgram.py +17 -14
  10. rasa/core/channels/voice_stream/tts/azure.py +3 -1
  11. rasa/core/channels/voice_stream/tts/cartesia.py +3 -3
  12. rasa/core/channels/voice_stream/tts/tts_engine.py +10 -1
  13. rasa/core/channels/voice_stream/voice_channel.py +48 -18
  14. rasa/core/information_retrieval/qdrant.py +1 -0
  15. rasa/core/nlg/contextual_response_rephraser.py +2 -2
  16. rasa/core/persistor.py +93 -49
  17. rasa/core/policies/enterprise_search_policy.py +5 -5
  18. rasa/core/policies/flows/flow_executor.py +18 -8
  19. rasa/core/policies/intentless_policy.py +9 -5
  20. rasa/core/processor.py +7 -5
  21. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +2 -1
  22. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +9 -0
  23. rasa/e2e_test/aggregate_test_stats_calculator.py +11 -1
  24. rasa/e2e_test/assertions.py +133 -16
  25. rasa/e2e_test/assertions_schema.yml +23 -0
  26. rasa/e2e_test/e2e_test_runner.py +2 -2
  27. rasa/engine/loader.py +12 -0
  28. rasa/engine/validation.py +310 -86
  29. rasa/model_manager/config.py +8 -0
  30. rasa/model_manager/model_api.py +166 -61
  31. rasa/model_manager/runner_service.py +31 -26
  32. rasa/model_manager/trainer_service.py +14 -23
  33. rasa/model_manager/warm_rasa_process.py +187 -0
  34. rasa/model_service.py +3 -5
  35. rasa/model_training.py +3 -1
  36. rasa/shared/constants.py +27 -5
  37. rasa/shared/core/constants.py +1 -1
  38. rasa/shared/core/domain.py +8 -31
  39. rasa/shared/core/flows/yaml_flows_io.py +13 -4
  40. rasa/shared/importers/importer.py +19 -2
  41. rasa/shared/importers/rasa.py +5 -1
  42. rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
  43. rasa/shared/providers/_configs/litellm_router_client_config.py +29 -9
  44. rasa/shared/providers/_utils.py +79 -0
  45. rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
  46. rasa/shared/providers/embedding/litellm_router_embedding_client.py +1 -1
  47. rasa/shared/providers/llm/_base_litellm_client.py +26 -0
  48. rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
  49. rasa/shared/providers/llm/litellm_router_llm_client.py +56 -1
  50. rasa/shared/providers/llm/self_hosted_llm_client.py +4 -28
  51. rasa/shared/providers/router/_base_litellm_router_client.py +35 -1
  52. rasa/shared/utils/common.py +30 -3
  53. rasa/shared/utils/health_check/health_check.py +26 -24
  54. rasa/shared/utils/yaml.py +116 -31
  55. rasa/studio/data_handler.py +3 -1
  56. rasa/studio/upload.py +119 -57
  57. rasa/telemetry.py +3 -1
  58. rasa/tracing/config.py +1 -1
  59. rasa/validator.py +40 -4
  60. rasa/version.py +1 -1
  61. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/METADATA +2 -2
  62. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/RECORD +65 -63
  63. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/NOTICE +0 -0
  64. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/WHEEL +0 -0
  65. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,10 @@
1
1
  import asyncio
2
+ from functools import wraps
2
3
  import os
3
4
  from http import HTTPStatus
4
- from typing import Any, Dict, Optional
5
+ from typing import Any, Callable, Dict, Optional, Union
5
6
  import dotenv
7
+ import psutil
6
8
  from sanic import Blueprint, Sanic, response
7
9
  from sanic.response import json
8
10
  from sanic.exceptions import NotFound
@@ -18,6 +20,7 @@ from rasa.model_manager.runner_service import (
18
20
  BotSession,
19
21
  BotSessionStatus,
20
22
  fetch_remote_model_to_dir,
23
+ fetch_size_of_remote_model,
21
24
  run_bot,
22
25
  terminate_bot,
23
26
  update_bot_status,
@@ -37,6 +40,10 @@ from rasa.model_manager.utils import (
37
40
  models_base_path,
38
41
  subpath,
39
42
  )
43
+ from rasa.model_manager.warm_rasa_process import (
44
+ initialize_warm_rasa_process,
45
+ shutdown_warm_rasa_processes,
46
+ )
40
47
 
41
48
  dotenv.load_dotenv()
42
49
 
@@ -111,7 +118,7 @@ async def continuously_update_process_status() -> None:
111
118
  except Exception as e:
112
119
  structlogger.error("model_api.update_process_status.error", error=str(e))
113
120
  finally:
114
- await asyncio.sleep(1)
121
+ await asyncio.sleep(0.1)
115
122
 
116
123
 
117
124
  def internal_blueprint() -> Blueprint:
@@ -124,61 +131,112 @@ def internal_blueprint() -> Blueprint:
124
131
  structlogger.debug("model_api.cleanup_processes.started")
125
132
  cleanup_training_processes()
126
133
  cleanup_bot_processes()
134
+ shutdown_warm_rasa_processes()
127
135
 
128
- @bp.on_request # type: ignore[misc]
129
- async def limit_parallel_training_requests(request: Request) -> Any:
130
- """Limit the number of parallel training requests."""
131
- from rasa.model_manager.config import MAX_PARALLEL_TRAININGS
132
-
133
- if not request.url.endswith("/training"):
134
- return None
135
-
136
- running_requests = len(
137
- [
138
- training
139
- for training in trainings.values()
140
- if training.status == TrainingSessionStatus.RUNNING
141
- and training.process.poll() is None
142
- ]
143
- )
144
-
145
- if running_requests >= int(MAX_PARALLEL_TRAININGS):
146
- return response.json(
147
- {
148
- "message": f"Too many parallel training requests, above "
149
- f"the limit of {MAX_PARALLEL_TRAININGS}. "
150
- f"Retry later or increase your server's "
151
- f"memory and CPU resources."
152
- },
153
- status=HTTPStatus.TOO_MANY_REQUESTS,
154
- )
136
+ @bp.after_server_start
137
+ async def create_warm_rasa_processes(
138
+ app: Sanic, loop: asyncio.AbstractEventLoop
139
+ ) -> None:
140
+ """Create warm Rasa processes to speed up future training and bot runs."""
141
+ structlogger.debug("model_api.create_warm_rasa_processes.started")
142
+ initialize_warm_rasa_process()
155
143
 
156
- @bp.on_request # type: ignore[misc]
157
- async def limit_parallel_bot_runs(request: Request) -> Any:
158
- """Limit the number of parallel bot runs."""
159
- from rasa.model_manager.config import MAX_PARALLEL_BOT_RUNS
160
-
161
- if not request.url.endswith("/bot"):
162
- return None
144
+ def limit_parallel_training_requests() -> Callable[[Callable], Callable[..., Any]]:
145
+ """Limit the number of parallel training requests."""
163
146
 
164
- running_requests = len(
165
- [
166
- bot
167
- for bot in running_bots.values()
168
- if bot.status in {BotSessionStatus.RUNNING, BotSessionStatus.QUEUED}
169
- ]
170
- )
147
+ def decorator(f: Callable) -> Callable:
148
+ @wraps(f)
149
+ def decorated(*args: Any, **kwargs: Any) -> Any:
150
+ running_requests = len(
151
+ [
152
+ training
153
+ for training in trainings.values()
154
+ if training.status == TrainingSessionStatus.RUNNING
155
+ and training.process.poll() is None
156
+ ]
157
+ )
158
+
159
+ if running_requests >= int(config.MAX_PARALLEL_TRAININGS):
160
+ return response.json(
161
+ {
162
+ "message": f"Too many parallel training requests, above "
163
+ f"the limit of {config.MAX_PARALLEL_TRAININGS}. "
164
+ f"Retry later or increase your server's "
165
+ f"memory and CPU resources."
166
+ },
167
+ status=HTTPStatus.TOO_MANY_REQUESTS,
168
+ )
169
+ return f(*args, **kwargs)
170
+
171
+ return decorated
172
+
173
+ return decorator
174
+
175
+ def limit_parallel_bot_runs() -> Callable[[Callable], Callable[..., Any]]:
176
+ """Limit the number of parallel training requests."""
171
177
 
172
- if running_requests >= int(MAX_PARALLEL_BOT_RUNS):
173
- return response.json(
174
- {
175
- "message": f"Too many parallel bot runs, above "
176
- f"the limit of {MAX_PARALLEL_BOT_RUNS}. "
177
- f"Retry later or increase your server's "
178
- f"memory and CPU resources."
179
- },
180
- status=HTTPStatus.TOO_MANY_REQUESTS,
181
- )
178
+ def decorator(f: Callable) -> Callable:
179
+ @wraps(f)
180
+ def decorated(*args: Any, **kwargs: Any) -> Any:
181
+ running_requests = len(
182
+ [
183
+ bot
184
+ for bot in running_bots.values()
185
+ if bot.status
186
+ in {BotSessionStatus.RUNNING, BotSessionStatus.QUEUED}
187
+ ]
188
+ )
189
+
190
+ if running_requests >= int(config.MAX_PARALLEL_BOT_RUNS):
191
+ return response.json(
192
+ {
193
+ "message": f"Too many parallel bot runs, above "
194
+ f"the limit of {config.MAX_PARALLEL_BOT_RUNS}. "
195
+ f"Retry later or increase your server's "
196
+ f"memory and CPU resources."
197
+ },
198
+ status=HTTPStatus.TOO_MANY_REQUESTS,
199
+ )
200
+
201
+ return f(*args, **kwargs)
202
+
203
+ return decorated
204
+
205
+ return decorator
206
+
207
+ def ensure_minimum_disk_space() -> Callable[[Callable], Callable[..., Any]]:
208
+ """Ensure that there is enough disk space before starting a new process."""
209
+ min_required_disk_space = 1024 * 1024 * config.MIN_REQUIRED_DISCSPACE_MB
210
+
211
+ def decorator(f: Callable) -> Callable:
212
+ @wraps(f)
213
+ def decorated(*args: Any, **kwargs: Any) -> Any:
214
+ if os.path.exists(config.SERVER_BASE_WORKING_DIRECTORY):
215
+ free_space_bytes = psutil.disk_usage(
216
+ config.SERVER_BASE_WORKING_DIRECTORY
217
+ ).free
218
+ structlogger.debug(
219
+ "model_api.storage.available_disk_space",
220
+ available_space_mb=free_space_bytes / 1024 / 1024,
221
+ )
222
+
223
+ if free_space_bytes < min_required_disk_space:
224
+ return response.json(
225
+ {
226
+ "message": (
227
+ f"Less than {config.MIN_REQUIRED_DISCSPACE_MB} MB "
228
+ f"of free disk space available. "
229
+ f"Please free up some space on the model service."
230
+ )
231
+ },
232
+ status=HTTPStatus.INSUFFICIENT_STORAGE,
233
+ )
234
+
235
+ return f(*args, **kwargs)
236
+
237
+ return decorated
238
+
239
+ return decorator
182
240
 
183
241
  @bp.get("/")
184
242
  async def health(request: Request) -> response.HTTPResponse:
@@ -190,6 +248,7 @@ def internal_blueprint() -> Blueprint:
190
248
  "deployment_id": bot.deployment_id,
191
249
  "status": bot.status,
192
250
  "internal_url": bot.internal_url,
251
+ "returncode": bot.returncode,
193
252
  "url": bot.url,
194
253
  }
195
254
  for bot in running_bots.values()
@@ -227,6 +286,8 @@ def internal_blueprint() -> Blueprint:
227
286
  return json({"training_sessions": sessions, "total_number": len(sessions)})
228
287
 
229
288
  @bp.post("/training")
289
+ @limit_parallel_training_requests()
290
+ @ensure_minimum_disk_space()
230
291
  async def start_training(request: Request) -> response.HTTPResponse:
231
292
  """Start a new training session."""
232
293
  data = request.json
@@ -277,7 +338,7 @@ def internal_blueprint() -> Blueprint:
277
338
  "progress": training.progress,
278
339
  "model_name": training.model_name,
279
340
  "status": training.status,
280
- "logs": get_logs_content(training_id),
341
+ "logs": get_logs_content(training.log_id),
281
342
  }
282
343
  )
283
344
  else:
@@ -295,6 +356,8 @@ def internal_blueprint() -> Blueprint:
295
356
  return json({"training_id": training_id})
296
357
 
297
358
  @bp.post("/bot")
359
+ @limit_parallel_bot_runs()
360
+ @ensure_minimum_disk_space()
298
361
  async def start_bot(request: Request) -> response.HTTPResponse:
299
362
  data = request.json
300
363
  deployment_id: Optional[str] = data.get("deployment_id")
@@ -359,8 +422,9 @@ def internal_blueprint() -> Blueprint:
359
422
  {
360
423
  "deployment_id": deployment_id,
361
424
  "status": bot.status,
425
+ "returncode": bot.returncode,
362
426
  "url": bot.url,
363
- "logs": get_logs_content(deployment_id),
427
+ "logs": get_logs_content(bot.log_id),
364
428
  }
365
429
  )
366
430
 
@@ -370,26 +434,47 @@ def internal_blueprint() -> Blueprint:
370
434
  {
371
435
  "deployment_id": bot.deployment_id,
372
436
  "status": bot.status,
437
+ "returncode": bot.returncode,
373
438
  "url": bot.url,
374
439
  }
375
440
  for bot in running_bots.values()
376
441
  ]
377
442
  return json({"deployment_sessions": bots, "total_number": len(bots)})
378
443
 
379
- @bp.route("/models/<model_name>")
380
- async def send_model(request: Request, model_name: str) -> response.HTTPResponse:
444
+ @bp.route("/models/<model_name>", methods=["GET"])
445
+ async def send_model(
446
+ request: Request, model_name: str
447
+ ) -> Union[response.ResponseStream, response.HTTPResponse]:
381
448
  try:
382
449
  model_path = path_to_model(model_name)
383
450
 
384
- if not model_path:
385
- return json({"message": "Model not found"}, status=404)
451
+ # get size of model file
452
+ model_size = os.stat(model_path)
386
453
 
387
- return await response.file(model_path)
454
+ return await response.file_stream(
455
+ model_path, headers={"Content-Length": str(model_size.st_size)}
456
+ )
388
457
  except NotFound:
389
458
  return json({"message": "Model not found"}, status=404)
390
459
  except ModelNotFound:
391
460
  return json({"message": "Model not found"}, status=404)
392
461
 
462
+ @bp.route("/models/<model_name>", methods=["HEAD"])
463
+ async def head_model(request: Request, model_name: str) -> response.HTTPResponse:
464
+ try:
465
+ model_size = size_of_model(model_name)
466
+
467
+ structlogger.debug(
468
+ "model_api.internal.head_model",
469
+ model_name=model_name,
470
+ size=model_size,
471
+ )
472
+ return response.raw(
473
+ b"", status=200, headers={"Content-Length": str(model_size)}
474
+ )
475
+ except ModelNotFound:
476
+ return response.raw(b"", status=404)
477
+
393
478
  return bp
394
479
 
395
480
 
@@ -432,6 +517,26 @@ def external_blueprint() -> Blueprint:
432
517
  return bp
433
518
 
434
519
 
520
+ def size_of_model(model_name: str) -> Optional[int]:
521
+ """Return the size of a model."""
522
+ model_file_name = f"{model_name}.{MODEL_ARCHIVE_EXTENSION}"
523
+ model_path = subpath(models_base_path(), model_file_name)
524
+
525
+ if os.path.exists(model_path):
526
+ return os.path.getsize(model_path)
527
+
528
+ if config.SERVER_MODEL_REMOTE_STORAGE:
529
+ structlogger.debug(
530
+ "model_api.storage.fetching_remote_model_size",
531
+ model_name=model_file_name,
532
+ )
533
+ return fetch_size_of_remote_model(
534
+ model_file_name,
535
+ config.SERVER_MODEL_REMOTE_STORAGE,
536
+ )
537
+ raise ModelNotFound("Model not found.")
538
+
539
+
435
540
  def path_to_model(model_name: str) -> Optional[str]:
436
541
  """Return the path to a local model."""
437
542
  model_file_name = f"{model_name}.{MODEL_ARCHIVE_EXTENSION}"
@@ -451,4 +556,4 @@ def path_to_model(model_name: str) -> Optional[str]:
451
556
  config.SERVER_MODEL_REMOTE_STORAGE,
452
557
  )
453
558
 
454
- return None
559
+ raise ModelNotFound("Model not found.")
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  import shutil
3
- from typing import Dict
3
+ from typing import Dict, Optional
4
4
  import aiohttp
5
5
  import structlog
6
6
  import subprocess
@@ -16,7 +16,8 @@ from rasa.model_manager.utils import (
16
16
  from rasa.constants import MODEL_ARCHIVE_EXTENSION
17
17
 
18
18
  from rasa.model_manager import config
19
- from rasa.model_manager.utils import logs_path, ensure_base_directory_exists
19
+ from rasa.model_manager.utils import logs_path
20
+ from rasa.model_manager.warm_rasa_process import start_rasa_process
20
21
 
21
22
  structlogger = structlog.get_logger()
22
23
 
@@ -40,6 +41,8 @@ class BotSession(BaseModel):
40
41
  url: str
41
42
  internal_url: str
42
43
  port: int
44
+ log_id: str
45
+ returncode: Optional[int] = None
43
46
 
44
47
  def is_alive(self) -> bool:
45
48
  """Check if the bot is alive."""
@@ -81,9 +84,6 @@ async def is_bot_startup_finished(bot: BotSession) -> bool:
81
84
  async with session.get(f"{bot.internal_url}/license") as resp:
82
85
  return resp.status == 200
83
86
  except aiohttp.client_exceptions.ClientConnectorError:
84
- structlogger.debug(
85
- "model_runner.bot.not_running_yet", deployment_id=bot.deployment_id
86
- )
87
87
  return False
88
88
 
89
89
 
@@ -95,6 +95,7 @@ def set_bot_status_to_stopped(bot: BotSession) -> None:
95
95
  status=bot.process.returncode,
96
96
  )
97
97
  bot.status = BotSessionStatus.STOPPED
98
+ bot.returncode = bot.process.returncode
98
99
 
99
100
 
100
101
  def set_bot_status_to_running(bot: BotSession) -> None:
@@ -181,18 +182,25 @@ def fetch_remote_model_to_dir(
181
182
  raise ModelNotFound() from e
182
183
 
183
184
 
185
+ def fetch_size_of_remote_model(model_name: str, storage_type: str) -> int:
186
+ """Fetch the size of the model from remote storage."""
187
+ from rasa.core.persistor import get_persistor
188
+
189
+ persistor = get_persistor(storage_type)
190
+
191
+ # we now there must be a persistor, because the config is set
192
+ # this is here to please the type checker for the call below
193
+ assert persistor is not None
194
+
195
+ return persistor.size_of_persisted_model(model_name=model_name)
196
+
197
+
184
198
  def start_bot_process(
185
199
  deployment_id: str, bot_base_path: str, base_url_path: str
186
200
  ) -> BotSession:
187
201
  port = get_open_port()
188
- log_path = logs_path(deployment_id)
189
-
190
- ensure_base_directory_exists(log_path)
191
202
 
192
- full_command = [
193
- config.RASA_PYTHON_PATH,
194
- "-m",
195
- "rasa.__main__",
203
+ arguments = [
196
204
  "run",
197
205
  "--endpoints",
198
206
  f"{bot_base_path}/endpoints.yml",
@@ -207,35 +215,30 @@ def start_bot_process(
207
215
  structlogger.debug(
208
216
  "model_runner.bot.starting_command",
209
217
  deployment_id=deployment_id,
210
- command=" ".join(full_command),
218
+ arguments=" ".join(arguments),
211
219
  )
212
220
 
213
- process = subprocess.Popen(
214
- full_command,
215
- cwd=bot_base_path,
216
- stdout=open(log_path, "w"),
217
- stderr=subprocess.STDOUT,
218
- env=os.environ.copy(),
219
- )
221
+ warm_process = start_rasa_process(cwd=bot_base_path, arguments=arguments)
220
222
 
221
223
  internal_bot_url = f"http://localhost:{port}"
222
224
 
223
225
  structlogger.info(
224
226
  "model_runner.bot.starting",
225
227
  deployment_id=deployment_id,
226
- log=log_path,
228
+ log=logs_path(warm_process.log_id),
227
229
  url=internal_bot_url,
228
230
  port=port,
229
- pid=process.pid,
231
+ pid=warm_process.process.pid,
230
232
  )
231
233
 
232
234
  return BotSession(
233
235
  deployment_id=deployment_id,
234
236
  status=BotSessionStatus.QUEUED,
235
- process=process,
237
+ process=warm_process.process,
236
238
  url=f"{base_url_path}?deployment_id={deployment_id}",
237
239
  internal_url=internal_bot_url,
238
240
  port=port,
241
+ log_id=warm_process.log_id,
239
242
  )
240
243
 
241
244
 
@@ -246,10 +249,11 @@ def run_bot(
246
249
  encoded_configs: Dict[str, str],
247
250
  ) -> BotSession:
248
251
  """Deploy a bot based on a given training id."""
249
- bot_base_path = bot_path(deployment_id)
250
- prepare_bot_directory(bot_base_path, model_name, encoded_configs)
252
+ with structlog.contextvars.bound_contextvars(model_name=model_name):
253
+ bot_base_path = bot_path(deployment_id)
254
+ prepare_bot_directory(bot_base_path, model_name, encoded_configs)
251
255
 
252
- return start_bot_process(deployment_id, bot_base_path, base_url_path)
256
+ return start_bot_process(deployment_id, bot_base_path, base_url_path)
253
257
 
254
258
 
255
259
  async def update_bot_status(bot: BotSession) -> None:
@@ -274,6 +278,7 @@ def terminate_bot(bot: BotSession) -> None:
274
278
  status=bot.process.returncode,
275
279
  )
276
280
  bot.status = BotSessionStatus.STOPPED
281
+ bot.returncode = bot.process.returncode
277
282
  except ProcessLookupError:
278
283
  structlogger.debug(
279
284
  "model_runner.stop_bot.process_not_found",
@@ -13,6 +13,9 @@ from pydantic import BaseModel, ConfigDict
13
13
  from enum import Enum
14
14
 
15
15
  from rasa.model_manager import config
16
+ from rasa.model_manager.warm_rasa_process import (
17
+ start_rasa_process,
18
+ )
16
19
  from rasa.model_training import generate_random_model_name
17
20
  from rasa.model_manager.utils import ensure_base_directory_exists, logs_path
18
21
 
@@ -40,6 +43,7 @@ class TrainingSession(BaseModel):
40
43
  model_name: str
41
44
  status: TrainingSessionStatus
42
45
  process: subprocess.Popen
46
+ log_id: str
43
47
 
44
48
  def is_status_indicating_alive(self) -> bool:
45
49
  """Check if the training is running."""
@@ -244,19 +248,12 @@ def start_training_process(
244
248
  client_id: str,
245
249
  training_base_path: str,
246
250
  ) -> TrainingSession:
247
- log_path = logs_path(training_id)
248
-
249
- ensure_base_directory_exists(log_path)
250
-
251
251
  model_name = generate_random_model_name()
252
252
  # Start the training in a subprocess
253
253
  # set the working directory to the training directory
254
254
  # run the rasa train command as a subprocess, activating poetry before running
255
255
  # pipe the stdout and stderr to the same file
256
- full_command = [
257
- config.RASA_PYTHON_PATH,
258
- "-m",
259
- "rasa.__main__",
256
+ arguments = [
260
257
  "train",
261
258
  "--debug",
262
259
  "--data",
@@ -274,7 +271,7 @@ def start_training_process(
274
271
  ]
275
272
 
276
273
  if config.SERVER_MODEL_REMOTE_STORAGE:
277
- full_command.extend(
274
+ arguments.extend(
278
275
  [
279
276
  "--keep-local-model-copy",
280
277
  "--remote-storage",
@@ -282,27 +279,20 @@ def start_training_process(
282
279
  ]
283
280
  )
284
281
 
285
- structlogger.debug("model_trainer.training_command", command=" ".join(full_command))
286
-
287
- envs = os.environ.copy()
288
- envs["RASA_TELEMETRY_ENABLED"] = "false"
289
-
290
- process = subprocess.Popen(
291
- full_command,
292
- cwd=training_base_path,
293
- stdout=open(log_path, "w"),
294
- stderr=subprocess.STDOUT,
295
- env=envs,
282
+ structlogger.debug(
283
+ "model_trainer.training_arguments", arguments=" ".join(arguments)
296
284
  )
297
285
 
286
+ warm_process = start_rasa_process(cwd=training_base_path, arguments=arguments)
287
+
298
288
  structlogger.info(
299
289
  "model_trainer.training_started",
300
290
  training_id=training_id,
301
291
  assistant_id=assistant_id,
302
292
  model_name=model_name,
303
293
  client_id=client_id,
304
- log=log_path,
305
- pid=process.pid,
294
+ log=logs_path(warm_process.log_id),
295
+ pid=warm_process.process.pid,
306
296
  )
307
297
 
308
298
  return TrainingSession(
@@ -312,7 +302,8 @@ def start_training_process(
312
302
  model_name=model_name,
313
303
  progress=0,
314
304
  status=TrainingSessionStatus.RUNNING,
315
- process=process, # Store the process handle
305
+ process=warm_process.process, # Store the process handle
306
+ log_id=warm_process.log_id,
316
307
  )
317
308
 
318
309