rasa-pro 3.11.0rc3__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.
- rasa/__main__.py +9 -3
- rasa/cli/studio/upload.py +0 -15
- rasa/cli/utils.py +1 -1
- rasa/core/channels/development_inspector.py +4 -1
- rasa/core/channels/voice_stream/asr/asr_engine.py +19 -1
- rasa/core/channels/voice_stream/asr/azure.py +11 -2
- rasa/core/channels/voice_stream/asr/deepgram.py +4 -3
- rasa/core/channels/voice_stream/tts/azure.py +3 -1
- rasa/core/channels/voice_stream/tts/cartesia.py +3 -3
- rasa/core/channels/voice_stream/tts/tts_engine.py +10 -1
- rasa/core/information_retrieval/qdrant.py +1 -0
- rasa/core/persistor.py +93 -49
- rasa/core/policies/flows/flow_executor.py +18 -8
- rasa/core/processor.py +7 -5
- rasa/e2e_test/aggregate_test_stats_calculator.py +11 -1
- rasa/e2e_test/assertions.py +133 -16
- rasa/e2e_test/assertions_schema.yml +23 -0
- rasa/e2e_test/e2e_test_runner.py +2 -2
- rasa/engine/loader.py +12 -0
- rasa/engine/validation.py +291 -79
- rasa/model_manager/config.py +8 -0
- rasa/model_manager/model_api.py +166 -61
- rasa/model_manager/runner_service.py +31 -26
- rasa/model_manager/trainer_service.py +14 -23
- rasa/model_manager/warm_rasa_process.py +187 -0
- rasa/model_service.py +3 -5
- rasa/model_training.py +3 -1
- rasa/shared/constants.py +22 -0
- rasa/shared/core/domain.py +8 -5
- rasa/shared/core/flows/yaml_flows_io.py +13 -4
- rasa/shared/importers/importer.py +19 -2
- rasa/shared/importers/rasa.py +5 -1
- rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
- rasa/shared/providers/_utils.py +79 -0
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
- rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
- rasa/shared/utils/common.py +29 -2
- rasa/shared/utils/health_check/health_check.py +26 -24
- rasa/shared/utils/yaml.py +116 -31
- rasa/studio/data_handler.py +3 -1
- rasa/studio/upload.py +119 -57
- rasa/validator.py +40 -4
- rasa/version.py +1 -1
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/METADATA +2 -2
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/RECORD +48 -46
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/entry_points.txt +0 -0
rasa/model_manager/model_api.py
CHANGED
|
@@ -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.
|
|
129
|
-
async def
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
385
|
-
|
|
451
|
+
# get size of model file
|
|
452
|
+
model_size = os.stat(model_path)
|
|
386
453
|
|
|
387
|
-
return await response.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
218
|
+
arguments=" ".join(arguments),
|
|
211
219
|
)
|
|
212
220
|
|
|
213
|
-
|
|
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=
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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=
|
|
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
|
|