rasa-pro 3.10.16__py3-none-any.whl → 3.11.0__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 +31 -15
- rasa/api.py +12 -2
- rasa/cli/arguments/default_arguments.py +24 -4
- rasa/cli/arguments/run.py +15 -0
- rasa/cli/arguments/shell.py +5 -1
- rasa/cli/arguments/train.py +17 -9
- rasa/cli/evaluate.py +7 -7
- rasa/cli/inspect.py +19 -7
- rasa/cli/interactive.py +1 -0
- rasa/cli/llm_fine_tuning.py +11 -14
- rasa/cli/project_templates/calm/config.yml +5 -7
- rasa/cli/project_templates/calm/endpoints.yml +15 -2
- rasa/cli/project_templates/tutorial/config.yml +8 -5
- rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
- rasa/cli/project_templates/tutorial/data/patterns.yml +5 -0
- rasa/cli/project_templates/tutorial/domain.yml +14 -0
- rasa/cli/project_templates/tutorial/endpoints.yml +5 -0
- rasa/cli/run.py +7 -0
- rasa/cli/scaffold.py +4 -2
- rasa/cli/studio/upload.py +0 -15
- rasa/cli/train.py +14 -53
- rasa/cli/utils.py +14 -11
- rasa/cli/x.py +7 -7
- rasa/constants.py +3 -1
- rasa/core/actions/action.py +77 -33
- rasa/core/actions/action_hangup.py +29 -0
- rasa/core/actions/action_repeat_bot_messages.py +89 -0
- rasa/core/actions/e2e_stub_custom_action_executor.py +5 -1
- rasa/core/actions/http_custom_action_executor.py +4 -0
- rasa/core/agent.py +2 -2
- rasa/core/brokers/kafka.py +3 -1
- rasa/core/brokers/pika.py +3 -1
- rasa/core/channels/__init__.py +10 -6
- rasa/core/channels/channel.py +41 -4
- rasa/core/channels/development_inspector.py +150 -46
- rasa/core/channels/inspector/README.md +1 -1
- rasa/core/channels/inspector/dist/assets/{arc-b6e548fe.js → arc-bc141fb2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-fa03ac9e.js → c4Diagram-d0fbc5ce-be2db283.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-ee67392a.js → classDiagram-936ed81e-55366915.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-9b283fae.js → classDiagram-v2-c3cb15f1-bb529518.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{createText-62fc7601-8b6fcc2a.js → createText-62fc7601-b0ec81d6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-22e77f4f.js → edges-f2ad444c-6166330c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-60ffc87f.js → erDiagram-9d236eb7-5ccc6a8e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-9dd802e4.js → flowDb-1972c806-fca3bfe4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-5fa1912f.js → flowDiagram-7ea5b25a-4739080f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-622a1fd2.js → flowchart-elk-definition-abe16c3d-7c1b0e0f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-e285a63a.js → ganttDiagram-9b5ea136-772fd050.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-f237bdca.js → gitGraphDiagram-99d0ae7c-8eae1dc9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-4b03d70e.js → index-2c4b9a3b-f55afcdf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/index-e7cef9de.js +1317 -0
- rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-72a0fa5f.js → infoDiagram-736b4530-124d4a14.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-82218c41.js → journeyDiagram-df861f2b-7c4fae44.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-78cff630.js → layout-b9885fb6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-5038b469.js → line-7c59abb6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-c4fc4098.js → linear-4776f780.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-c33c8ea6.js → mindmap-definition-beec6740-2332c46c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-a8d03059.js → pieDiagram-dbbf0591-8fb39303.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-6a0e56b2.js → quadrantDiagram-4d7f4fd6-3c7180a2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-2dc7c7bd.js → requirementDiagram-6fc4c22a-e910bcb8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-2360fe39.js → sankeyDiagram-8f13d901-ead16c89.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-41b9f9ad.js → sequenceDiagram-b655622a-29a02a19.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-0aad326f.js → stateDiagram-59f0c015-042b3137.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-9847d984.js → stateDiagram-v2-2b26beab-2178c0f3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-080da4f6-564d890e.js → styles-080da4f6-23ffa4fc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-38957613.js → styles-3dcbcfbf-94f59763.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9c745c82-f0fc6921.js → styles-9c745c82-78a6bebc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-ef3c5a77.js → svgDrawCommon-4835440b-eae2a6f6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-bf3e91c1.js → timeline-definition-5b62e21b-5c968d92.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-4d4026c0.js → xychartDiagram-2b33534f-fd3db0d5.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +18 -17
- rasa/core/channels/inspector/index.html +17 -16
- rasa/core/channels/inspector/package.json +5 -1
- rasa/core/channels/inspector/src/App.tsx +118 -68
- rasa/core/channels/inspector/src/components/Chat.tsx +95 -0
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +11 -10
- rasa/core/channels/inspector/src/components/DialogueStack.tsx +10 -25
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -3
- rasa/core/channels/inspector/src/helpers/audiostream.ts +165 -0
- rasa/core/channels/inspector/src/helpers/formatters.test.ts +10 -0
- rasa/core/channels/inspector/src/helpers/formatters.ts +107 -41
- rasa/core/channels/inspector/src/helpers/utils.ts +92 -7
- rasa/core/channels/inspector/src/types.ts +21 -1
- rasa/core/channels/inspector/yarn.lock +94 -1
- rasa/core/channels/rest.py +51 -46
- rasa/core/channels/socketio.py +28 -1
- rasa/core/channels/telegram.py +1 -1
- rasa/core/channels/twilio.py +1 -1
- rasa/core/channels/{audiocodes.py → voice_ready/audiocodes.py} +122 -69
- rasa/core/channels/{voice_aware → voice_ready}/jambonz.py +26 -8
- rasa/core/channels/{voice_aware → voice_ready}/jambonz_protocol.py +57 -5
- rasa/core/channels/{twilio_voice.py → voice_ready/twilio_voice.py} +64 -28
- rasa/core/channels/voice_ready/utils.py +37 -0
- rasa/core/channels/voice_stream/asr/__init__.py +0 -0
- rasa/core/channels/voice_stream/asr/asr_engine.py +89 -0
- rasa/core/channels/voice_stream/asr/asr_event.py +18 -0
- rasa/core/channels/voice_stream/asr/azure.py +129 -0
- rasa/core/channels/voice_stream/asr/deepgram.py +90 -0
- rasa/core/channels/voice_stream/audio_bytes.py +8 -0
- rasa/core/channels/voice_stream/browser_audio.py +107 -0
- rasa/core/channels/voice_stream/call_state.py +23 -0
- rasa/core/channels/voice_stream/tts/__init__.py +0 -0
- rasa/core/channels/voice_stream/tts/azure.py +106 -0
- rasa/core/channels/voice_stream/tts/cartesia.py +118 -0
- rasa/core/channels/voice_stream/tts/tts_cache.py +27 -0
- rasa/core/channels/voice_stream/tts/tts_engine.py +58 -0
- rasa/core/channels/voice_stream/twilio_media_streams.py +173 -0
- rasa/core/channels/voice_stream/util.py +57 -0
- rasa/core/channels/voice_stream/voice_channel.py +427 -0
- rasa/core/information_retrieval/qdrant.py +1 -0
- rasa/core/nlg/contextual_response_rephraser.py +45 -17
- rasa/{nlu → core}/persistor.py +203 -68
- rasa/core/policies/enterprise_search_policy.py +119 -63
- rasa/core/policies/flows/flow_executor.py +15 -22
- rasa/core/policies/intentless_policy.py +83 -28
- rasa/core/processor.py +25 -0
- rasa/core/run.py +12 -2
- rasa/core/secrets_manager/constants.py +4 -0
- rasa/core/secrets_manager/factory.py +8 -0
- rasa/core/secrets_manager/vault.py +11 -1
- rasa/core/training/interactive.py +33 -34
- rasa/core/utils.py +47 -21
- rasa/dialogue_understanding/coexistence/llm_based_router.py +41 -14
- rasa/dialogue_understanding/commands/__init__.py +6 -0
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +60 -0
- rasa/dialogue_understanding/commands/session_end_command.py +61 -0
- rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
- rasa/dialogue_understanding/commands/utils.py +5 -0
- rasa/dialogue_understanding/generator/constants.py +2 -0
- rasa/dialogue_understanding/generator/flow_retrieval.py +47 -9
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +38 -15
- rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +35 -13
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +3 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +60 -13
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +53 -0
- rasa/dialogue_understanding/patterns/repeat.py +37 -0
- rasa/dialogue_understanding/patterns/user_silence.py +37 -0
- rasa/dialogue_understanding/processor/command_processor.py +21 -1
- rasa/e2e_test/aggregate_test_stats_calculator.py +1 -11
- rasa/e2e_test/assertions.py +136 -61
- rasa/e2e_test/assertions_schema.yml +23 -0
- rasa/e2e_test/e2e_test_case.py +85 -6
- rasa/e2e_test/e2e_test_runner.py +2 -3
- rasa/e2e_test/utils/e2e_yaml_utils.py +1 -1
- rasa/engine/graph.py +3 -10
- rasa/engine/loader.py +12 -0
- rasa/engine/recipes/config_files/default_config.yml +0 -3
- rasa/engine/recipes/default_recipe.py +0 -1
- rasa/engine/recipes/graph_recipe.py +0 -1
- rasa/engine/runner/dask.py +2 -2
- rasa/engine/storage/local_model_storage.py +12 -42
- rasa/engine/storage/storage.py +1 -5
- rasa/engine/validation.py +527 -74
- rasa/model_manager/__init__.py +0 -0
- rasa/model_manager/config.py +40 -0
- rasa/model_manager/model_api.py +559 -0
- rasa/model_manager/runner_service.py +286 -0
- rasa/model_manager/socket_bridge.py +146 -0
- rasa/model_manager/studio_jwt_auth.py +86 -0
- rasa/model_manager/trainer_service.py +325 -0
- rasa/model_manager/utils.py +87 -0
- rasa/model_manager/warm_rasa_process.py +187 -0
- rasa/model_service.py +112 -0
- rasa/model_training.py +42 -23
- rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
- rasa/server.py +4 -2
- rasa/shared/constants.py +60 -8
- rasa/shared/core/constants.py +13 -0
- rasa/shared/core/domain.py +107 -50
- rasa/shared/core/events.py +29 -0
- rasa/shared/core/flows/flow.py +5 -0
- rasa/shared/core/flows/flows_list.py +19 -6
- rasa/shared/core/flows/flows_yaml_schema.json +10 -0
- rasa/shared/core/flows/utils.py +39 -0
- rasa/shared/core/flows/validation.py +121 -0
- rasa/shared/core/flows/yaml_flows_io.py +15 -27
- rasa/shared/core/slots.py +5 -0
- rasa/shared/importers/importer.py +59 -41
- rasa/shared/importers/multi_project.py +23 -11
- rasa/shared/importers/rasa.py +12 -3
- rasa/shared/importers/remote_importer.py +196 -0
- rasa/shared/importers/utils.py +3 -1
- rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
- rasa/shared/nlu/training_data/training_data.py +18 -19
- rasa/shared/providers/_configs/litellm_router_client_config.py +220 -0
- rasa/shared/providers/_configs/model_group_config.py +167 -0
- rasa/shared/providers/_configs/openai_client_config.py +1 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +73 -0
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -0
- rasa/shared/providers/_configs/utils.py +16 -0
- rasa/shared/providers/_utils.py +79 -0
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +13 -29
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +54 -21
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +135 -0
- rasa/shared/providers/llm/_base_litellm_client.py +34 -22
- rasa/shared/providers/llm/azure_openai_llm_client.py +50 -29
- rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
- rasa/shared/providers/llm/litellm_router_llm_client.py +182 -0
- rasa/shared/providers/llm/rasa_llm_client.py +112 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +5 -29
- rasa/shared/providers/mappings.py +19 -0
- rasa/shared/providers/router/__init__.py +0 -0
- rasa/shared/providers/router/_base_litellm_router_client.py +183 -0
- rasa/shared/providers/router/router_client.py +73 -0
- rasa/shared/utils/common.py +40 -24
- rasa/shared/utils/health_check/__init__.py +0 -0
- rasa/shared/utils/health_check/embeddings_health_check_mixin.py +31 -0
- rasa/shared/utils/health_check/health_check.py +258 -0
- rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
- rasa/shared/utils/io.py +27 -6
- rasa/shared/utils/llm.py +354 -44
- rasa/shared/utils/schemas/events.py +2 -0
- rasa/shared/utils/schemas/model_config.yml +0 -10
- rasa/shared/utils/yaml.py +181 -38
- rasa/studio/data_handler.py +3 -1
- rasa/studio/upload.py +160 -74
- rasa/telemetry.py +94 -17
- rasa/tracing/config.py +3 -1
- rasa/tracing/instrumentation/attribute_extractors.py +95 -18
- rasa/tracing/instrumentation/instrumentation.py +121 -0
- rasa/utils/common.py +5 -0
- rasa/utils/endpoints.py +27 -1
- rasa/utils/io.py +8 -16
- rasa/utils/log_utils.py +9 -2
- rasa/utils/sanic_error_handler.py +32 -0
- rasa/validator.py +110 -16
- rasa/version.py +1 -1
- {rasa_pro-3.10.16.dist-info → rasa_pro-3.11.0.dist-info}/METADATA +16 -14
- {rasa_pro-3.10.16.dist-info → rasa_pro-3.11.0.dist-info}/RECORD +236 -185
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +0 -1
- rasa/core/channels/inspector/dist/assets/index-a5d3e69d.js +0 -1040
- rasa/core/channels/voice_aware/utils.py +0 -20
- rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +0 -407
- /rasa/core/channels/{voice_aware → voice_ready}/__init__.py +0 -0
- /rasa/core/channels/{voice_native → voice_stream}/__init__.py +0 -0
- {rasa_pro-3.10.16.dist-info → rasa_pro-3.11.0.dist-info}/NOTICE +0 -0
- {rasa_pro-3.10.16.dist-info → rasa_pro-3.11.0.dist-info}/WHEEL +0 -0
- {rasa_pro-3.10.16.dist-info → rasa_pro-3.11.0.dist-info}/entry_points.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
DEFAULT_SERVER_BASE_WORKING_DIRECTORY = "working-data"
|
|
5
|
+
|
|
6
|
+
SERVER_BASE_WORKING_DIRECTORY = os.environ.get(
|
|
7
|
+
"RASA_MODEL_SERVER_BASE_DIRECTORY", DEFAULT_SERVER_BASE_WORKING_DIRECTORY
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
SERVER_PORT = os.environ.get("RASA_MODEL_SERVER_PORT", 8000)
|
|
11
|
+
|
|
12
|
+
SERVER_BASE_URL = os.environ.get("RASA_MODEL_SERVER_BASE_URL", None)
|
|
13
|
+
|
|
14
|
+
# defaults to storing on the local hard drive
|
|
15
|
+
SERVER_MODEL_REMOTE_STORAGE = os.environ.get("RASA_REMOTE_STORAGE", None)
|
|
16
|
+
|
|
17
|
+
# The path to the python executable that is running this script
|
|
18
|
+
# we will use the same python to run training / bots
|
|
19
|
+
RASA_PYTHON_PATH = sys.executable
|
|
20
|
+
|
|
21
|
+
# the max limit for parallel training requests
|
|
22
|
+
DEFAULT_MAX_PARALLEL_TRAININGS = 10
|
|
23
|
+
|
|
24
|
+
MAX_PARALLEL_TRAININGS = os.getenv(
|
|
25
|
+
"MAX_PARALLEL_TRAININGS", DEFAULT_MAX_PARALLEL_TRAININGS
|
|
26
|
+
)
|
|
27
|
+
# the max limit for parallel running bots
|
|
28
|
+
DEFAULT_MAX_PARALLEL_BOT_RUNS = 10
|
|
29
|
+
|
|
30
|
+
MAX_PARALLEL_BOT_RUNS = os.getenv(
|
|
31
|
+
"MAX_PARALLEL_BOT_RUNS", DEFAULT_MAX_PARALLEL_BOT_RUNS
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
DEFAULT_SERVER_PATH_PREFIX = "talk"
|
|
35
|
+
|
|
36
|
+
DEFAULT_MIN_REQUIRED_DISCSPACE_MB = 1
|
|
37
|
+
|
|
38
|
+
MIN_REQUIRED_DISCSPACE_MB = int(
|
|
39
|
+
os.getenv("MIN_REQUIRED_DISCSPACE_MB", DEFAULT_MIN_REQUIRED_DISCSPACE_MB)
|
|
40
|
+
)
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from functools import wraps
|
|
3
|
+
import os
|
|
4
|
+
from http import HTTPStatus
|
|
5
|
+
from typing import Any, Callable, Dict, Optional, Union
|
|
6
|
+
import dotenv
|
|
7
|
+
import psutil
|
|
8
|
+
from sanic import Blueprint, Sanic, response
|
|
9
|
+
from sanic.response import json
|
|
10
|
+
from sanic.exceptions import NotFound
|
|
11
|
+
from sanic.request import Request
|
|
12
|
+
import structlog
|
|
13
|
+
from socketio import AsyncServer
|
|
14
|
+
|
|
15
|
+
from rasa.exceptions import ModelNotFound
|
|
16
|
+
from rasa.model_manager import config
|
|
17
|
+
from rasa.model_manager.config import SERVER_BASE_URL
|
|
18
|
+
from rasa.constants import MODEL_ARCHIVE_EXTENSION
|
|
19
|
+
from rasa.model_manager.runner_service import (
|
|
20
|
+
BotSession,
|
|
21
|
+
BotSessionStatus,
|
|
22
|
+
fetch_remote_model_to_dir,
|
|
23
|
+
fetch_size_of_remote_model,
|
|
24
|
+
run_bot,
|
|
25
|
+
terminate_bot,
|
|
26
|
+
update_bot_status,
|
|
27
|
+
)
|
|
28
|
+
from rasa.model_manager.socket_bridge import create_bridge_server
|
|
29
|
+
from rasa.model_manager.trainer_service import (
|
|
30
|
+
TrainingSession,
|
|
31
|
+
TrainingSessionStatus,
|
|
32
|
+
run_training,
|
|
33
|
+
terminate_training,
|
|
34
|
+
update_training_status,
|
|
35
|
+
)
|
|
36
|
+
from rasa.model_manager.utils import (
|
|
37
|
+
InvalidPathException,
|
|
38
|
+
get_logs_content,
|
|
39
|
+
logs_base_path,
|
|
40
|
+
models_base_path,
|
|
41
|
+
subpath,
|
|
42
|
+
)
|
|
43
|
+
from rasa.model_manager.warm_rasa_process import (
|
|
44
|
+
initialize_warm_rasa_process,
|
|
45
|
+
shutdown_warm_rasa_processes,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
dotenv.load_dotenv()
|
|
49
|
+
|
|
50
|
+
structlogger = structlog.get_logger()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# A simple in-memory store for training sessions and running bots
|
|
54
|
+
trainings: Dict[str, TrainingSession] = {}
|
|
55
|
+
running_bots: Dict[str, BotSession] = {}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def prepare_working_directories() -> None:
|
|
59
|
+
"""Make sure all required directories exist."""
|
|
60
|
+
os.makedirs(logs_base_path(), exist_ok=True)
|
|
61
|
+
os.makedirs(models_base_path(), exist_ok=True)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cleanup_training_processes() -> None:
|
|
65
|
+
"""Terminate all running training processes."""
|
|
66
|
+
structlogger.debug("model_trainer.cleanup_processes.started")
|
|
67
|
+
running = list(trainings.values())
|
|
68
|
+
for training in running:
|
|
69
|
+
terminate_training(training)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def cleanup_bot_processes() -> None:
|
|
73
|
+
"""Terminate all running bot processes."""
|
|
74
|
+
structlogger.debug("model_runner.cleanup_processes.started")
|
|
75
|
+
running = list(running_bots.values())
|
|
76
|
+
for bot in running:
|
|
77
|
+
terminate_bot(bot)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def update_status_of_all_trainings() -> None:
|
|
81
|
+
"""Update the status of all training processes."""
|
|
82
|
+
running = list(trainings.values())
|
|
83
|
+
for training in running:
|
|
84
|
+
update_training_status(training)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def update_status_of_all_bots() -> None:
|
|
88
|
+
"""Update the status of all bot processes."""
|
|
89
|
+
# we need to get the values first, because (since we are async and waiting
|
|
90
|
+
# within the loop) some other job on the asyncio loop could change the dict
|
|
91
|
+
# (adding or removing). python doesn't like if you change the size of a dict
|
|
92
|
+
# while iterating over it and will raise a RuntimeError. so we get the values
|
|
93
|
+
# first and iterate over them to avoid that.
|
|
94
|
+
running = list(running_bots.values())
|
|
95
|
+
for bot in running:
|
|
96
|
+
await update_bot_status(bot)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def base_server_url(request: Request) -> str:
|
|
100
|
+
"""Return the base URL of the server."""
|
|
101
|
+
if SERVER_BASE_URL:
|
|
102
|
+
return SERVER_BASE_URL.rstrip("/")
|
|
103
|
+
else:
|
|
104
|
+
return f"{request.scheme}://{request.host}/{config.DEFAULT_SERVER_PATH_PREFIX}"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def continuously_update_process_status() -> None:
|
|
108
|
+
"""Regularly Update the status of all training and bot processes."""
|
|
109
|
+
structlogger.debug("model_api.update_process_status.started")
|
|
110
|
+
|
|
111
|
+
while True:
|
|
112
|
+
try:
|
|
113
|
+
update_status_of_all_trainings()
|
|
114
|
+
await update_status_of_all_bots()
|
|
115
|
+
except asyncio.exceptions.CancelledError:
|
|
116
|
+
structlogger.debug("model_api.update_process_status.cancelled")
|
|
117
|
+
break
|
|
118
|
+
except Exception as e:
|
|
119
|
+
structlogger.error("model_api.update_process_status.error", error=str(e))
|
|
120
|
+
finally:
|
|
121
|
+
await asyncio.sleep(0.1)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def internal_blueprint() -> Blueprint:
|
|
125
|
+
"""Create a blueprint for the model manager API."""
|
|
126
|
+
bp = Blueprint("model_api_internal")
|
|
127
|
+
|
|
128
|
+
@bp.before_server_stop
|
|
129
|
+
async def cleanup_processes(app: Sanic, loop: asyncio.AbstractEventLoop) -> None:
|
|
130
|
+
"""Terminate all running processes before the server stops."""
|
|
131
|
+
structlogger.debug("model_api.cleanup_processes.started")
|
|
132
|
+
cleanup_training_processes()
|
|
133
|
+
cleanup_bot_processes()
|
|
134
|
+
shutdown_warm_rasa_processes()
|
|
135
|
+
|
|
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()
|
|
143
|
+
|
|
144
|
+
def limit_parallel_training_requests() -> Callable[[Callable], Callable[..., Any]]:
|
|
145
|
+
"""Limit the number of parallel training requests."""
|
|
146
|
+
|
|
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."""
|
|
177
|
+
|
|
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
|
|
240
|
+
|
|
241
|
+
@bp.get("/")
|
|
242
|
+
async def health(request: Request) -> response.HTTPResponse:
|
|
243
|
+
return json(
|
|
244
|
+
{
|
|
245
|
+
"status": "ok",
|
|
246
|
+
"bots": [
|
|
247
|
+
{
|
|
248
|
+
"deployment_id": bot.deployment_id,
|
|
249
|
+
"status": bot.status,
|
|
250
|
+
"internal_url": bot.internal_url,
|
|
251
|
+
"returncode": bot.returncode,
|
|
252
|
+
"url": bot.url,
|
|
253
|
+
}
|
|
254
|
+
for bot in running_bots.values()
|
|
255
|
+
],
|
|
256
|
+
"trainings": [
|
|
257
|
+
{
|
|
258
|
+
"training_id": training.training_id,
|
|
259
|
+
"assistant_id": training.assistant_id,
|
|
260
|
+
"client_id": training.client_id,
|
|
261
|
+
"progress": training.progress,
|
|
262
|
+
"status": training.status,
|
|
263
|
+
}
|
|
264
|
+
for training in trainings.values()
|
|
265
|
+
],
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@bp.get("/training")
|
|
270
|
+
async def get_training_list(request: Request) -> response.HTTPResponse:
|
|
271
|
+
"""Return a list of all training sessions for an assistant."""
|
|
272
|
+
assistant_id = request.args.get("assistant_id")
|
|
273
|
+
sessions = [
|
|
274
|
+
{
|
|
275
|
+
"training_id": session.training_id,
|
|
276
|
+
"assistant_id": session.assistant_id,
|
|
277
|
+
"client_id": session.client_id,
|
|
278
|
+
"progress": session.progress,
|
|
279
|
+
"status": session.status,
|
|
280
|
+
"model_name": session.model_name,
|
|
281
|
+
"runtime_metadata": None,
|
|
282
|
+
}
|
|
283
|
+
for session in trainings.values()
|
|
284
|
+
if session.assistant_id == assistant_id
|
|
285
|
+
]
|
|
286
|
+
return json({"training_sessions": sessions, "total_number": len(sessions)})
|
|
287
|
+
|
|
288
|
+
@bp.post("/training")
|
|
289
|
+
@limit_parallel_training_requests()
|
|
290
|
+
@ensure_minimum_disk_space()
|
|
291
|
+
async def start_training(request: Request) -> response.HTTPResponse:
|
|
292
|
+
"""Start a new training session."""
|
|
293
|
+
data = request.json
|
|
294
|
+
training_id: Optional[str] = data.get("id")
|
|
295
|
+
assistant_id: Optional[str] = data.get("assistant_id")
|
|
296
|
+
client_id: Optional[str] = data.get("client_id")
|
|
297
|
+
encoded_training_data: Dict[str, str] = data.get("bot_config", {}).get(
|
|
298
|
+
"data", {}
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if training_id in trainings:
|
|
302
|
+
# fail, because there apparently is already a training with this id
|
|
303
|
+
return json({"message": "Training with this id already exists"}, status=409)
|
|
304
|
+
|
|
305
|
+
if not assistant_id:
|
|
306
|
+
return json({"message": "Assistant id is required"}, status=400)
|
|
307
|
+
|
|
308
|
+
if not training_id:
|
|
309
|
+
return json({"message": "Training id is required"}, status=400)
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
# file deepcode ignore PT: path traversal is prevented
|
|
313
|
+
# by the `subpath` function found in the `rasa.model_manager.utils` module
|
|
314
|
+
training_session = run_training(
|
|
315
|
+
training_id=training_id,
|
|
316
|
+
assistant_id=assistant_id,
|
|
317
|
+
client_id=client_id,
|
|
318
|
+
encoded_training_data=encoded_training_data,
|
|
319
|
+
)
|
|
320
|
+
trainings[training_id] = training_session
|
|
321
|
+
return json(
|
|
322
|
+
{"training_id": training_id, "model_name": training_session.model_name}
|
|
323
|
+
)
|
|
324
|
+
except InvalidPathException as exc:
|
|
325
|
+
return json({"message": str(exc)}, status=403)
|
|
326
|
+
except Exception as exc:
|
|
327
|
+
return json({"message": str(exc)}, status=500)
|
|
328
|
+
|
|
329
|
+
@bp.get("/training/<training_id>")
|
|
330
|
+
async def get_training(request: Request, training_id: str) -> response.HTTPResponse:
|
|
331
|
+
"""Return the status of a training session."""
|
|
332
|
+
if training := trainings.get(training_id):
|
|
333
|
+
return json(
|
|
334
|
+
{
|
|
335
|
+
"training_id": training_id,
|
|
336
|
+
"assistant_id": training.assistant_id,
|
|
337
|
+
"client_id": training.client_id,
|
|
338
|
+
"progress": training.progress,
|
|
339
|
+
"model_name": training.model_name,
|
|
340
|
+
"status": training.status,
|
|
341
|
+
"logs": get_logs_content(training.log_id),
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
else:
|
|
345
|
+
return json({"message": "Training not found"}, status=404)
|
|
346
|
+
|
|
347
|
+
@bp.delete("/training/<training_id>")
|
|
348
|
+
async def stop_training(
|
|
349
|
+
request: Request, training_id: str
|
|
350
|
+
) -> response.HTTPResponse:
|
|
351
|
+
# this is a no-op if the training is already done
|
|
352
|
+
if not (training := trainings.get(training_id)):
|
|
353
|
+
return json({"message": "Training session not found"}, status=404)
|
|
354
|
+
|
|
355
|
+
terminate_training(training)
|
|
356
|
+
return json({"training_id": training_id})
|
|
357
|
+
|
|
358
|
+
@bp.post("/bot")
|
|
359
|
+
@limit_parallel_bot_runs()
|
|
360
|
+
@ensure_minimum_disk_space()
|
|
361
|
+
async def start_bot(request: Request) -> response.HTTPResponse:
|
|
362
|
+
data = request.json
|
|
363
|
+
deployment_id: Optional[str] = data.get("deployment_id")
|
|
364
|
+
model_name: Optional[str] = data.get("model_name")
|
|
365
|
+
encoded_configs: Dict[str, str] = data.get("bot_config", {})
|
|
366
|
+
|
|
367
|
+
if deployment_id in running_bots:
|
|
368
|
+
# fail, because there apparently is already a bot running with this id
|
|
369
|
+
return json(
|
|
370
|
+
{"message": "Bot with this deployment id already exists"}, status=409
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if not deployment_id:
|
|
374
|
+
return json({"message": "Deployment id is required"}, status=400)
|
|
375
|
+
|
|
376
|
+
if not model_name:
|
|
377
|
+
return json({"message": "Model name is required"}, status=400)
|
|
378
|
+
|
|
379
|
+
base_url_path = base_server_url(request)
|
|
380
|
+
try:
|
|
381
|
+
bot_session = run_bot(
|
|
382
|
+
deployment_id,
|
|
383
|
+
model_name,
|
|
384
|
+
base_url_path,
|
|
385
|
+
encoded_configs,
|
|
386
|
+
)
|
|
387
|
+
running_bots[deployment_id] = bot_session
|
|
388
|
+
return json(
|
|
389
|
+
{
|
|
390
|
+
"deployment_id": deployment_id,
|
|
391
|
+
"status": bot_session.status,
|
|
392
|
+
"url": bot_session.url,
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
except ModelNotFound:
|
|
396
|
+
return json(
|
|
397
|
+
{"message": f"Model with name '{model_name}' could not be found."},
|
|
398
|
+
status=404,
|
|
399
|
+
)
|
|
400
|
+
except Exception as e:
|
|
401
|
+
return json({"message": str(e)}, status=500)
|
|
402
|
+
|
|
403
|
+
@bp.delete("/bot/<deployment_id>")
|
|
404
|
+
async def stop_bot(request: Request, deployment_id: str) -> response.HTTPResponse:
|
|
405
|
+
bot = running_bots.get(deployment_id)
|
|
406
|
+
if bot is None:
|
|
407
|
+
return json({"message": "Bot not found"}, status=404)
|
|
408
|
+
|
|
409
|
+
terminate_bot(bot)
|
|
410
|
+
|
|
411
|
+
return json(
|
|
412
|
+
{"deployment_id": deployment_id, "status": bot.status, "url": bot.url}
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
@bp.get("/bot/<deployment_id>")
|
|
416
|
+
async def get_bot(request: Request, deployment_id: str) -> response.HTTPResponse:
|
|
417
|
+
bot = running_bots.get(deployment_id)
|
|
418
|
+
if bot is None:
|
|
419
|
+
return json({"message": "Bot not found"}, status=404)
|
|
420
|
+
|
|
421
|
+
return json(
|
|
422
|
+
{
|
|
423
|
+
"deployment_id": deployment_id,
|
|
424
|
+
"status": bot.status,
|
|
425
|
+
"returncode": bot.returncode,
|
|
426
|
+
"url": bot.url,
|
|
427
|
+
"logs": get_logs_content(bot.log_id),
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
@bp.get("/bot")
|
|
432
|
+
async def list_bots(request: Request) -> response.HTTPResponse:
|
|
433
|
+
bots = [
|
|
434
|
+
{
|
|
435
|
+
"deployment_id": bot.deployment_id,
|
|
436
|
+
"status": bot.status,
|
|
437
|
+
"returncode": bot.returncode,
|
|
438
|
+
"url": bot.url,
|
|
439
|
+
}
|
|
440
|
+
for bot in running_bots.values()
|
|
441
|
+
]
|
|
442
|
+
return json({"deployment_sessions": bots, "total_number": len(bots)})
|
|
443
|
+
|
|
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]:
|
|
448
|
+
try:
|
|
449
|
+
model_path = path_to_model(model_name)
|
|
450
|
+
|
|
451
|
+
# get size of model file
|
|
452
|
+
model_size = os.stat(model_path)
|
|
453
|
+
|
|
454
|
+
return await response.file_stream(
|
|
455
|
+
model_path, headers={"Content-Length": str(model_size.st_size)}
|
|
456
|
+
)
|
|
457
|
+
except NotFound:
|
|
458
|
+
return json({"message": "Model not found"}, status=404)
|
|
459
|
+
except ModelNotFound:
|
|
460
|
+
return json({"message": "Model not found"}, status=404)
|
|
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
|
+
|
|
478
|
+
return bp
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def external_blueprint() -> Blueprint:
|
|
482
|
+
"""Create a blueprint for the model manager API."""
|
|
483
|
+
from rasa.core.channels.socketio import SocketBlueprint
|
|
484
|
+
|
|
485
|
+
sio = AsyncServer(async_mode="sanic", cors_allowed_origins=[])
|
|
486
|
+
bp = SocketBlueprint(sio, "", "model_api_external")
|
|
487
|
+
|
|
488
|
+
create_bridge_server(sio, running_bots)
|
|
489
|
+
|
|
490
|
+
@bp.get("/health")
|
|
491
|
+
async def health(request: Request) -> response.HTTPResponse:
|
|
492
|
+
return json(
|
|
493
|
+
{
|
|
494
|
+
"status": "ok",
|
|
495
|
+
"bots": [
|
|
496
|
+
{
|
|
497
|
+
"deployment_id": bot.deployment_id,
|
|
498
|
+
"status": bot.status,
|
|
499
|
+
"internal_url": bot.internal_url,
|
|
500
|
+
"url": bot.url,
|
|
501
|
+
}
|
|
502
|
+
for bot in running_bots.values()
|
|
503
|
+
],
|
|
504
|
+
"trainings": [
|
|
505
|
+
{
|
|
506
|
+
"training_id": training.training_id,
|
|
507
|
+
"assistant_id": training.assistant_id,
|
|
508
|
+
"client_id": training.client_id,
|
|
509
|
+
"progress": training.progress,
|
|
510
|
+
"status": training.status,
|
|
511
|
+
}
|
|
512
|
+
for training in trainings.values()
|
|
513
|
+
],
|
|
514
|
+
}
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
return bp
|
|
518
|
+
|
|
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
|
+
|
|
540
|
+
def path_to_model(model_name: str) -> Optional[str]:
|
|
541
|
+
"""Return the path to a local model."""
|
|
542
|
+
model_file_name = f"{model_name}.{MODEL_ARCHIVE_EXTENSION}"
|
|
543
|
+
model_path = subpath(models_base_path(), model_file_name)
|
|
544
|
+
|
|
545
|
+
if os.path.exists(model_path):
|
|
546
|
+
return model_path
|
|
547
|
+
|
|
548
|
+
if config.SERVER_MODEL_REMOTE_STORAGE:
|
|
549
|
+
structlogger.info(
|
|
550
|
+
"model_api.storage.fetching_remote_model",
|
|
551
|
+
model_name=model_file_name,
|
|
552
|
+
)
|
|
553
|
+
return fetch_remote_model_to_dir(
|
|
554
|
+
model_file_name,
|
|
555
|
+
models_base_path(),
|
|
556
|
+
config.SERVER_MODEL_REMOTE_STORAGE,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
raise ModelNotFound("Model not found.")
|