open-swarm 0.1.1745275181__py3-none-any.whl → 0.1.1748636295__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.
- open_swarm-0.1.1748636295.dist-info/METADATA +257 -0
- open_swarm-0.1.1748636295.dist-info/RECORD +89 -0
- {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/WHEEL +2 -1
- open_swarm-0.1.1748636295.dist-info/entry_points.txt +3 -0
- open_swarm-0.1.1748636295.dist-info/top_level.txt +1 -0
- swarm/__init__.py +2 -0
- swarm/agent/agent.py +49 -0
- swarm/auth.py +48 -113
- swarm/consumers.py +0 -19
- swarm/core.py +411 -0
- swarm/extensions/blueprint/__init__.py +16 -30
- swarm/extensions/blueprint/agent_utils.py +45 -0
- swarm/extensions/blueprint/blueprint_base.py +562 -0
- swarm/extensions/blueprint/blueprint_discovery.py +112 -0
- swarm/extensions/blueprint/django_utils.py +79 -181
- swarm/extensions/blueprint/interactive_mode.py +72 -67
- swarm/extensions/blueprint/output_utils.py +82 -0
- swarm/{core → extensions/blueprint}/spinner.py +21 -30
- swarm/extensions/cli/cli_args.py +0 -6
- swarm/extensions/cli/commands/blueprint_management.py +9 -47
- swarm/extensions/cli/commands/config_management.py +6 -5
- swarm/extensions/cli/commands/edit_config.py +7 -16
- swarm/extensions/cli/commands/list_blueprints.py +1 -1
- swarm/extensions/cli/commands/validate_env.py +4 -11
- swarm/extensions/cli/commands/validate_envvars.py +6 -6
- swarm/extensions/cli/interactive_shell.py +2 -16
- swarm/extensions/config/config_loader.py +345 -107
- swarm/{core → extensions/config}/config_manager.py +38 -50
- swarm/{core → extensions/config}/server_config.py +0 -32
- swarm/extensions/launchers/build_launchers.py +14 -0
- swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
- swarm/extensions/launchers/swarm_api.py +64 -8
- swarm/extensions/launchers/swarm_cli.py +300 -8
- swarm/extensions/mcp/__init__.py +1 -0
- swarm/extensions/mcp/cache_utils.py +32 -0
- swarm/extensions/mcp/mcp_client.py +233 -0
- swarm/extensions/mcp/mcp_tool_provider.py +135 -0
- swarm/extensions/mcp/mcp_utils.py +260 -0
- swarm/llm/chat_completion.py +166 -0
- swarm/serializers.py +5 -96
- swarm/settings.py +133 -85
- swarm/types.py +91 -0
- swarm/urls.py +74 -57
- swarm/utils/context_utils.py +4 -10
- swarm/utils/general_utils.py +0 -21
- swarm/utils/redact.py +36 -23
- swarm/views/api_views.py +39 -48
- swarm/views/chat_views.py +76 -236
- swarm/views/core_views.py +87 -80
- swarm/views/model_views.py +121 -64
- swarm/views/utils.py +439 -65
- swarm/views/web_views.py +2 -2
- open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
- open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
- open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
- swarm/blueprints/README.md +0 -68
- swarm/blueprints/blueprint_audit_status.json +0 -27
- swarm/blueprints/chatbot/README.md +0 -40
- swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
- swarm/blueprints/chatbot/metadata.json +0 -23
- swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
- swarm/blueprints/chucks_angels/README.md +0 -11
- swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
- swarm/blueprints/chucks_angels/test_basic.py +0 -3
- swarm/blueprints/codey/CODEY.md +0 -15
- swarm/blueprints/codey/README.md +0 -115
- swarm/blueprints/codey/blueprint_codey.py +0 -1072
- swarm/blueprints/codey/codey_cli.py +0 -373
- swarm/blueprints/codey/instructions.md +0 -17
- swarm/blueprints/codey/metadata.json +0 -23
- swarm/blueprints/common/operation_box_utils.py +0 -83
- swarm/blueprints/digitalbutlers/README.md +0 -11
- swarm/blueprints/digitalbutlers/__init__.py +0 -1
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
- swarm/blueprints/digitalbutlers/test_basic.py +0 -3
- swarm/blueprints/divine_code/README.md +0 -3
- swarm/blueprints/divine_code/__init__.py +0 -10
- swarm/blueprints/divine_code/apps.py +0 -11
- swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
- swarm/blueprints/django_chat/apps.py +0 -6
- swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
- swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
- swarm/blueprints/django_chat/urls.py +0 -8
- swarm/blueprints/django_chat/views.py +0 -32
- swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
- swarm/blueprints/flock/README.md +0 -11
- swarm/blueprints/flock/__init__.py +0 -8
- swarm/blueprints/flock/blueprint_flock.py +0 -7
- swarm/blueprints/flock/test_basic.py +0 -3
- swarm/blueprints/geese/README.md +0 -10
- swarm/blueprints/geese/__init__.py +0 -8
- swarm/blueprints/geese/blueprint_geese.py +0 -384
- swarm/blueprints/geese/geese_cli.py +0 -102
- swarm/blueprints/jeeves/README.md +0 -41
- swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
- swarm/blueprints/jeeves/jeeves_cli.py +0 -55
- swarm/blueprints/jeeves/metadata.json +0 -24
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
- swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
- swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
- swarm/blueprints/poets/blueprint_poets.py +0 -546
- swarm/blueprints/poets/poets_cli.py +0 -23
- swarm/blueprints/rue_code/README.md +0 -8
- swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
- swarm/blueprints/rue_code/rue_code_cli.py +0 -43
- swarm/blueprints/stewie/apps.py +0 -12
- swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
- swarm/blueprints/stewie/models.py +0 -19
- swarm/blueprints/stewie/serializers.py +0 -10
- swarm/blueprints/stewie/settings.py +0 -17
- swarm/blueprints/stewie/urls.py +0 -11
- swarm/blueprints/stewie/views.py +0 -26
- swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
- swarm/blueprints/whinge_surf/README.md +0 -22
- swarm/blueprints/whinge_surf/__init__.py +0 -1
- swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
- swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
- swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
- swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
- swarm/blueprints/zeus/__init__.py +0 -2
- swarm/blueprints/zeus/apps.py +0 -4
- swarm/blueprints/zeus/blueprint_zeus.py +0 -270
- swarm/blueprints/zeus/zeus_cli.py +0 -13
- swarm/cli/async_input.py +0 -65
- swarm/cli/async_input_demo.py +0 -32
- swarm/core/agent_utils.py +0 -21
- swarm/core/blueprint_base.py +0 -769
- swarm/core/blueprint_discovery.py +0 -125
- swarm/core/blueprint_runner.py +0 -59
- swarm/core/blueprint_ux.py +0 -109
- swarm/core/build_launchers.py +0 -15
- swarm/core/cli/__init__.py +0 -1
- swarm/core/cli/commands/__init__.py +0 -1
- swarm/core/cli/commands/blueprint_management.py +0 -7
- swarm/core/cli/interactive_shell.py +0 -14
- swarm/core/cli/main.py +0 -50
- swarm/core/cli/utils/__init__.py +0 -1
- swarm/core/cli/utils/discover_commands.py +0 -18
- swarm/core/config_loader.py +0 -122
- swarm/core/output_utils.py +0 -193
- swarm/core/session_logger.py +0 -42
- swarm/core/slash_commands.py +0 -89
- swarm/core/swarm_api.py +0 -68
- swarm/core/swarm_cli.py +0 -216
- swarm/core/utils/__init__.py +0 -0
- swarm/extensions/blueprint/cli_handler.py +0 -197
- swarm/extensions/blueprint/runnable_blueprint.py +0 -42
- swarm/extensions/cli/utils/__init__.py +0 -1
- swarm/extensions/cli/utils/async_input.py +0 -46
- swarm/extensions/cli/utils/prompt_user.py +0 -3
- swarm/management/__init__.py +0 -0
- swarm/management/commands/__init__.py +0 -0
- swarm/management/commands/runserver.py +0 -58
- swarm/middleware.py +0 -65
- swarm/permissions.py +0 -38
- swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
- swarm/static/contrib/markedjs/marked.min.js +0 -6
- swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
- swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
- swarm/static/contrib/tabler-icons/archive.svg +0 -21
- swarm/static/contrib/tabler-icons/artboard.svg +0 -27
- swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
- swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
- swarm/static/contrib/tabler-icons/carambola.svg +0 -19
- swarm/static/contrib/tabler-icons/copy.svg +0 -20
- swarm/static/contrib/tabler-icons/download.svg +0 -21
- swarm/static/contrib/tabler-icons/edit.svg +0 -21
- swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
- swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
- swarm/static/contrib/tabler-icons/headset.svg +0 -22
- swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
- swarm/static/contrib/tabler-icons/message-star.svg +0 -22
- swarm/static/contrib/tabler-icons/message-x.svg +0 -23
- swarm/static/contrib/tabler-icons/message.svg +0 -21
- swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
- swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
- swarm/static/contrib/tabler-icons/robot.svg +0 -26
- swarm/static/contrib/tabler-icons/search.svg +0 -19
- swarm/static/contrib/tabler-icons/settings.svg +0 -20
- swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
- swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
- swarm/static/css/dropdown.css +0 -22
- swarm/static/htmx/htmx.min.js +0 -0
- swarm/static/js/dropdown.js +0 -23
- swarm/static/rest_mode/css/base.css +0 -470
- swarm/static/rest_mode/css/chat-history.css +0 -286
- swarm/static/rest_mode/css/chat.css +0 -251
- swarm/static/rest_mode/css/chatbot.css +0 -74
- swarm/static/rest_mode/css/chatgpt.css +0 -62
- swarm/static/rest_mode/css/colors/corporate.css +0 -74
- swarm/static/rest_mode/css/colors/pastel.css +0 -81
- swarm/static/rest_mode/css/colors/tropical.css +0 -82
- swarm/static/rest_mode/css/general.css +0 -142
- swarm/static/rest_mode/css/layout.css +0 -167
- swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
- swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
- swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
- swarm/static/rest_mode/css/messages.css +0 -84
- swarm/static/rest_mode/css/messenger.css +0 -135
- swarm/static/rest_mode/css/settings.css +0 -91
- swarm/static/rest_mode/css/simple.css +0 -44
- swarm/static/rest_mode/css/slack.css +0 -58
- swarm/static/rest_mode/css/style.css +0 -156
- swarm/static/rest_mode/css/theme.css +0 -30
- swarm/static/rest_mode/css/toast.css +0 -40
- swarm/static/rest_mode/js/auth.js +0 -9
- swarm/static/rest_mode/js/blueprint.js +0 -41
- swarm/static/rest_mode/js/blueprintUtils.js +0 -12
- swarm/static/rest_mode/js/chatLogic.js +0 -79
- swarm/static/rest_mode/js/debug.js +0 -63
- swarm/static/rest_mode/js/events.js +0 -98
- swarm/static/rest_mode/js/main.js +0 -19
- swarm/static/rest_mode/js/messages.js +0 -264
- swarm/static/rest_mode/js/messengerLogic.js +0 -355
- swarm/static/rest_mode/js/modules/apiService.js +0 -84
- swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
- swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
- swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
- swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
- swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
- swarm/static/rest_mode/js/modules/state.js +0 -7
- swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
- swarm/static/rest_mode/js/modules/validation.js +0 -23
- swarm/static/rest_mode/js/rendering.js +0 -119
- swarm/static/rest_mode/js/settings.js +0 -130
- swarm/static/rest_mode/js/sidebar.js +0 -94
- swarm/static/rest_mode/js/simpleLogic.js +0 -37
- swarm/static/rest_mode/js/slackLogic.js +0 -66
- swarm/static/rest_mode/js/splash.js +0 -76
- swarm/static/rest_mode/js/theme.js +0 -111
- swarm/static/rest_mode/js/toast.js +0 -36
- swarm/static/rest_mode/js/ui.js +0 -265
- swarm/static/rest_mode/js/validation.js +0 -57
- swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
- swarm/static/rest_mode/svg/arrow_down.svg +0 -5
- swarm/static/rest_mode/svg/arrow_left.svg +0 -5
- swarm/static/rest_mode/svg/arrow_right.svg +0 -5
- swarm/static/rest_mode/svg/arrow_up.svg +0 -5
- swarm/static/rest_mode/svg/attach.svg +0 -8
- swarm/static/rest_mode/svg/avatar.svg +0 -7
- swarm/static/rest_mode/svg/canvas.svg +0 -6
- swarm/static/rest_mode/svg/chat_history.svg +0 -4
- swarm/static/rest_mode/svg/close.svg +0 -5
- swarm/static/rest_mode/svg/copy.svg +0 -4
- swarm/static/rest_mode/svg/dark_mode.svg +0 -3
- swarm/static/rest_mode/svg/edit.svg +0 -5
- swarm/static/rest_mode/svg/layout.svg +0 -9
- swarm/static/rest_mode/svg/logo.svg +0 -29
- swarm/static/rest_mode/svg/logout.svg +0 -5
- swarm/static/rest_mode/svg/mobile.svg +0 -5
- swarm/static/rest_mode/svg/new_chat.svg +0 -4
- swarm/static/rest_mode/svg/not_visible.svg +0 -5
- swarm/static/rest_mode/svg/plus.svg +0 -7
- swarm/static/rest_mode/svg/run_code.svg +0 -6
- swarm/static/rest_mode/svg/save.svg +0 -4
- swarm/static/rest_mode/svg/search.svg +0 -6
- swarm/static/rest_mode/svg/settings.svg +0 -4
- swarm/static/rest_mode/svg/speaker.svg +0 -5
- swarm/static/rest_mode/svg/stop.svg +0 -6
- swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
- swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
- swarm/static/rest_mode/svg/toggle_off.svg +0 -6
- swarm/static/rest_mode/svg/toggle_on.svg +0 -6
- swarm/static/rest_mode/svg/trash.svg +0 -10
- swarm/static/rest_mode/svg/undo.svg +0 -3
- swarm/static/rest_mode/svg/visible.svg +0 -8
- swarm/static/rest_mode/svg/voice.svg +0 -10
- swarm/templates/account/login.html +0 -22
- swarm/templates/account/signup.html +0 -32
- swarm/templates/base.html +0 -30
- swarm/templates/chat.html +0 -43
- swarm/templates/index.html +0 -35
- swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
- swarm/templates/rest_mode/components/header.html +0 -45
- swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
- swarm/templates/rest_mode/components/settings_dialog.html +0 -97
- swarm/templates/rest_mode/components/splash_screen.html +0 -7
- swarm/templates/rest_mode/components/top_bar.html +0 -28
- swarm/templates/rest_mode/message_ui.html +0 -50
- swarm/templates/rest_mode/slackbot.html +0 -30
- swarm/templates/simple_blueprint_page.html +0 -24
- swarm/templates/websocket_partials/final_system_message.html +0 -3
- swarm/templates/websocket_partials/system_message.html +0 -4
- swarm/templates/websocket_partials/user_message.html +0 -5
- swarm/utils/ansi_box.py +0 -34
- swarm/utils/disable_tracing.py +0 -38
- swarm/utils/log_utils.py +0 -63
- swarm/utils/openai_patch.py +0 -33
- swarm/ux/ansi_box.py +0 -43
- swarm/ux/spinner.py +0 -53
- {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/licenses/LICENSE +0 -0
- /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
- /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
- /swarm/{core → extensions/config}/setup_wizard.py +0 -0
- /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
- /swarm/{core → extensions/config}/utils/logger.py +0 -0
- /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -1,114 +1,352 @@
|
|
1
|
-
|
1
|
+
"""
|
2
|
+
Configuration Loader for Open Swarm MCP Framework.
|
3
|
+
"""
|
4
|
+
|
2
5
|
import os
|
3
|
-
|
6
|
+
import json
|
7
|
+
import re
|
4
8
|
import logging
|
5
|
-
from typing import Dict,
|
9
|
+
from typing import Any, Dict, List, Tuple, Optional
|
10
|
+
from pathlib import Path
|
11
|
+
from dotenv import load_dotenv
|
12
|
+
# Import save_server_config carefully
|
13
|
+
try: from .server_config import save_server_config
|
14
|
+
except ImportError: save_server_config = None
|
15
|
+
from swarm.settings import DEBUG, BASE_DIR
|
16
|
+
from swarm.utils.redact import redact_sensitive_data
|
6
17
|
|
7
18
|
logger = logging.getLogger(__name__)
|
19
|
+
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
20
|
+
if not logger.handlers:
|
21
|
+
stream_handler = logging.StreamHandler()
|
22
|
+
formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s")
|
23
|
+
stream_handler.setFormatter(formatter)
|
24
|
+
logger.addHandler(stream_handler)
|
25
|
+
|
26
|
+
config: Dict[str, Any] = {}
|
27
|
+
load_dotenv()
|
28
|
+
logger.debug("Environment variables potentially loaded from .env file.")
|
8
29
|
|
9
|
-
|
10
|
-
|
11
|
-
# --- find_config_file, load_config, save_config, validate_config, get_profile_from_config, _substitute_env_vars_recursive ---
|
12
|
-
# (Keep these functions as they were)
|
13
|
-
def find_config_file( specific_path: Optional[str]=None, start_dir: Optional[Path]=None, default_dir: Optional[Path]=None,) -> Optional[Path]:
|
14
|
-
# 1. XDG config path
|
15
|
-
xdg_config = Path(os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))) / "swarm" / DEFAULT_CONFIG_FILENAME
|
16
|
-
if xdg_config.is_file():
|
17
|
-
logger.debug(f"Found config XDG: {xdg_config}")
|
18
|
-
return xdg_config.resolve()
|
19
|
-
# 2. User-specified path
|
20
|
-
if specific_path:
|
21
|
-
p = Path(specific_path)
|
22
|
-
return p.resolve() if p.is_file() else logger.warning(f"Specified config path DNE: {specific_path}") or None # Fall through
|
23
|
-
# 3. Upwards from start_dir
|
24
|
-
if start_dir:
|
25
|
-
current = start_dir.resolve()
|
26
|
-
while current != current.parent:
|
27
|
-
if (cp := current / DEFAULT_CONFIG_FILENAME).is_file():
|
28
|
-
logger.debug(f"Found config upwards: {cp}")
|
29
|
-
return cp.resolve()
|
30
|
-
current = current.parent
|
31
|
-
if (cp := current / DEFAULT_CONFIG_FILENAME).is_file():
|
32
|
-
logger.debug(f"Found config at root: {cp}")
|
33
|
-
return cp.resolve()
|
34
|
-
# 4. Default dir
|
35
|
-
if default_dir and (cp := default_dir.resolve() / DEFAULT_CONFIG_FILENAME).is_file():
|
36
|
-
logger.debug(f"Found config default: {cp}")
|
37
|
-
return cp.resolve()
|
38
|
-
# 5. CWD
|
39
|
-
cwd = Path.cwd()
|
40
|
-
if start_dir is None or cwd != start_dir.resolve():
|
41
|
-
if (cp := cwd / DEFAULT_CONFIG_FILENAME).is_file():
|
42
|
-
logger.debug(f"Found config cwd: {cp}")
|
43
|
-
return cp.resolve()
|
44
|
-
logger.debug(f"Config '{DEFAULT_CONFIG_FILENAME}' not found.")
|
45
|
-
return None
|
46
|
-
|
47
|
-
def load_config(config_path: Path) -> Dict[str, Any]:
|
48
|
-
logger.debug(f"Loading config from {config_path}")
|
30
|
+
def process_config(config_dict: dict) -> dict:
|
31
|
+
"""Processes config: resolves placeholders, merges external MCP."""
|
49
32
|
try:
|
50
|
-
|
51
|
-
logger.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
""
|
82
|
-
return
|
83
|
-
|
84
|
-
def
|
85
|
-
"""
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
33
|
+
resolved_config = resolve_placeholders(config_dict)
|
34
|
+
if logger.isEnabledFor(logging.DEBUG): logger.debug("Config after resolving placeholders: " + json.dumps(redact_sensitive_data(resolved_config), indent=2))
|
35
|
+
|
36
|
+
disable_merge = os.getenv("DISABLE_MCP_MERGE", "false").lower() in ("true", "1", "yes")
|
37
|
+
if not disable_merge:
|
38
|
+
if os.name == "nt": external_mcp_path = Path(os.getenv("APPDATA", Path.home())) / "Claude" / "claude_desktop_config.json"
|
39
|
+
else: external_mcp_path = Path.home() / ".vscode-server" / "data" / "User" / "globalStorage" / "rooveterinaryinc.roo-cline" / "settings" / "cline_mcp_settings.json"
|
40
|
+
|
41
|
+
if external_mcp_path.exists():
|
42
|
+
logger.info(f"Found external MCP settings file at: {external_mcp_path}")
|
43
|
+
try:
|
44
|
+
with open(external_mcp_path, "r") as mcp_file: external_mcp_config = json.load(mcp_file)
|
45
|
+
if logger.isEnabledFor(logging.DEBUG): logger.debug("Loaded external MCP settings: " + json.dumps(redact_sensitive_data(external_mcp_config), indent=2))
|
46
|
+
|
47
|
+
main_mcp_servers = resolved_config.get("mcpServers", {})
|
48
|
+
external_mcp_servers = external_mcp_config.get("mcpServers", {})
|
49
|
+
merged_mcp_servers = main_mcp_servers.copy()
|
50
|
+
servers_added_count = 0
|
51
|
+
for server_name, server_config in external_mcp_servers.items():
|
52
|
+
if server_name not in merged_mcp_servers and not server_config.get("disabled", False):
|
53
|
+
merged_mcp_servers[server_name] = server_config
|
54
|
+
servers_added_count += 1
|
55
|
+
if servers_added_count > 0:
|
56
|
+
resolved_config["mcpServers"] = merged_mcp_servers
|
57
|
+
logger.info(f"Merged {servers_added_count} MCP servers from external settings.")
|
58
|
+
if logger.isEnabledFor(logging.DEBUG): logger.debug("Merged MCP servers config: " + json.dumps(redact_sensitive_data(merged_mcp_servers), indent=2))
|
59
|
+
else: logger.debug("No new MCP servers added from external settings.")
|
60
|
+
except Exception as merge_err: logger.error(f"Failed to load/merge MCP settings from '{external_mcp_path}': {merge_err}", exc_info=logger.isEnabledFor(logging.DEBUG))
|
61
|
+
else: logger.debug(f"External MCP settings file not found at {external_mcp_path}. Skipping merge.")
|
62
|
+
else: logger.debug("MCP settings merge disabled via DISABLE_MCP_MERGE env var.")
|
63
|
+
except Exception as e: logger.error(f"Failed during configuration processing: {e}", exc_info=logger.isEnabledFor(logging.DEBUG)); raise
|
64
|
+
globals()["config"] = resolved_config
|
65
|
+
return resolved_config
|
66
|
+
|
67
|
+
def resolve_placeholders(obj: Any) -> Any:
|
68
|
+
"""Recursively resolve ${VAR_NAME} placeholders. Returns None if var not found."""
|
69
|
+
if isinstance(obj, dict): return {k: resolve_placeholders(v) for k, v in obj.items()}
|
70
|
+
elif isinstance(obj, list): return [resolve_placeholders(item) for item in obj]
|
71
|
+
elif isinstance(obj, str):
|
72
|
+
pattern = re.compile(r'\$\{(\w+)\}')
|
73
|
+
resolved_string = obj
|
74
|
+
placeholders_found = pattern.findall(obj)
|
75
|
+
all_resolved = True # Flag to track if all placeholders in string were resolved
|
76
|
+
for var_name in placeholders_found:
|
77
|
+
env_value = os.getenv(var_name)
|
78
|
+
placeholder = f'${{{var_name}}}'
|
79
|
+
if env_value is None:
|
80
|
+
logger.warning(f"Env var '{var_name}' not set for placeholder '{placeholder}'. Placeholder will resolve to None.")
|
81
|
+
# If only a placeholder exists, return None directly
|
82
|
+
if resolved_string == placeholder:
|
83
|
+
return None
|
84
|
+
# If placeholder is part of larger string, replace with empty string or marker?
|
85
|
+
# Let's replace with empty string for now to avoid partial resolution issues.
|
86
|
+
resolved_string = resolved_string.replace(placeholder, "")
|
87
|
+
all_resolved = False # Mark that not all placeholders resolved fully
|
88
|
+
else:
|
89
|
+
resolved_string = resolved_string.replace(placeholder, env_value)
|
90
|
+
if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Resolved placeholder '{placeholder}' using env var '{var_name}'.")
|
91
|
+
|
92
|
+
# If any placeholder failed to resolve in a mixed string, log it.
|
93
|
+
# If the original string was *only* an unresolved placeholder, we already returned None.
|
94
|
+
if not all_resolved and len(placeholders_found) > 0:
|
95
|
+
logger.warning(f"String '{obj}' contained unresolved placeholders. Result: '{resolved_string}'")
|
96
|
+
|
97
|
+
return resolved_string
|
98
|
+
else: return obj
|
99
|
+
|
100
|
+
def load_server_config(file_path: Optional[str] = None) -> dict:
|
101
|
+
"""Loads, resolves, and merges server config from JSON file."""
|
102
|
+
config_path: Optional[Path] = None
|
103
|
+
if file_path:
|
104
|
+
path_obj = Path(file_path)
|
105
|
+
if path_obj.is_file(): config_path = path_obj; logger.info(f"Using provided config file path: {config_path}")
|
106
|
+
else: logger.warning(f"Provided path '{file_path}' not found/not file. Searching standard locations.")
|
107
|
+
if not config_path:
|
108
|
+
current_dir = Path.cwd()
|
109
|
+
standard_paths = [ current_dir / "swarm_config.json", Path(BASE_DIR) / "swarm_config.json", Path.home() / ".swarm" / "swarm_config.json" ]
|
110
|
+
for candidate in standard_paths:
|
111
|
+
if candidate.is_file(): config_path = candidate; logger.info(f"Using config file found at: {config_path}"); break
|
112
|
+
if not config_path: raise FileNotFoundError(f"Config file 'swarm_config.json' not found in provided path or standard locations: {[str(p) for p in standard_paths]}")
|
113
|
+
logger.debug(f"Attempting to load config from: {config_path}")
|
114
|
+
try:
|
115
|
+
# Ensure reading with UTF-8 encoding
|
116
|
+
raw_config = json.loads(config_path.read_text(encoding='utf-8'))
|
117
|
+
if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Raw config loaded: {redact_sensitive_data(raw_config)}")
|
118
|
+
except json.JSONDecodeError as json_err:
|
119
|
+
logger.critical(f"Invalid JSON in config file {config_path}: {json_err}")
|
120
|
+
raise ValueError(f"Invalid JSON in config {config_path}: {json_err}") from json_err
|
121
|
+
except Exception as load_err:
|
122
|
+
logger.critical(f"Failed to read config file {config_path}: {load_err}")
|
123
|
+
raise ValueError(f"Failed to read config {config_path}") from load_err
|
124
|
+
try:
|
125
|
+
processed_config = process_config(raw_config)
|
126
|
+
globals()["config"] = processed_config
|
127
|
+
logger.info(f"Config loaded and processed from {config_path}")
|
128
|
+
return processed_config
|
129
|
+
except Exception as process_err: logger.critical(f"Failed to process config from {config_path}: {process_err}", exc_info=True); raise ValueError(f"Failed to process config from {config_path}") from process_err
|
130
|
+
|
131
|
+
# --- Start of Missing Functions ---
|
132
|
+
|
133
|
+
def are_required_mcp_servers_configured(required_servers: List[str], config_dict: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
134
|
+
"""Checks if required MCP servers are present in the config."""
|
135
|
+
if not required_servers: return True, []
|
136
|
+
mcp_servers = config_dict.get("mcpServers", {})
|
137
|
+
if not isinstance(mcp_servers, dict):
|
138
|
+
logger.warning("MCP servers configuration ('mcpServers') is missing or invalid.")
|
139
|
+
return False, required_servers # All are missing if section is invalid
|
140
|
+
|
141
|
+
missing = [server for server in required_servers if server not in mcp_servers]
|
142
|
+
if missing:
|
143
|
+
logger.warning(f"Required MCP servers are missing from configuration: {missing}")
|
144
|
+
return False, missing
|
145
|
+
else:
|
146
|
+
logger.debug("All required MCP servers are configured.")
|
147
|
+
return True, []
|
148
|
+
|
149
|
+
def validate_mcp_server_env(mcp_servers: Dict[str, Any], required_servers: Optional[List[str]] = None) -> None:
|
150
|
+
"""
|
151
|
+
Validates that required environment variables specified within MCP server
|
152
|
+
configurations are actually set in the environment. Assumes placeholders in
|
153
|
+
the config's `env` section values are *already resolved* before calling this.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
mcp_servers: Dictionary of MCP server configurations (placeholders resolved).
|
157
|
+
required_servers: Optional list of specific server names to validate. If None, validates all.
|
158
|
+
|
159
|
+
Raises:
|
160
|
+
ValueError: If a required environment variable for a validated server is not set.
|
161
|
+
"""
|
162
|
+
servers_to_validate = mcp_servers
|
163
|
+
if required_servers is not None:
|
164
|
+
servers_to_validate = {k: v for k, v in mcp_servers.items() if k in required_servers}
|
165
|
+
missing_keys = [k for k in required_servers if k not in mcp_servers]
|
166
|
+
if missing_keys: logger.warning(f"Required MCP servers missing during env validation: {missing_keys}")
|
167
|
+
|
168
|
+
logger.debug(f"Validating environment variables for MCP servers: {list(servers_to_validate.keys())}")
|
169
|
+
|
170
|
+
for server_name, server_config in servers_to_validate.items():
|
171
|
+
env_section = server_config.get("env", {})
|
172
|
+
if not isinstance(env_section, dict): logger.warning(f"'env' for MCP server '{server_name}' invalid. Skipping."); continue
|
173
|
+
logger.debug(f"Validating env for MCP server '{server_name}'.")
|
174
|
+
for env_key, env_spec in env_section.items():
|
175
|
+
# Determine if required (default is True)
|
176
|
+
is_required = env_spec.get("required", True) if isinstance(env_spec, dict) else True
|
177
|
+
if not is_required: logger.debug(f"Skipping optional env var '{env_key}' for '{server_name}'."); continue
|
178
|
+
|
179
|
+
# Get the RESOLVED value from the config dict
|
180
|
+
config_value = env_spec.get("value") if isinstance(env_spec, dict) else env_spec
|
181
|
+
|
182
|
+
# Check if the resolved value is missing or empty
|
183
|
+
if config_value is None or (isinstance(config_value, str) and not config_value.strip()):
|
184
|
+
# This check assumes resolve_placeholders returned None or empty for missing env vars
|
185
|
+
raise ValueError(f"Required env var '{env_key}' for MCP server '{server_name}' is missing or empty in resolved config.")
|
186
|
+
else: logger.debug(f"Env var '{env_key}' for '{server_name}' present in resolved config.")
|
187
|
+
|
188
|
+
def get_default_llm_config(config_dict: Dict[str, Any]) -> Dict[str, Any]:
|
189
|
+
"""Retrieves the config dict for the default LLM profile."""
|
190
|
+
selected_llm_name = os.getenv("DEFAULT_LLM", "default")
|
191
|
+
logger.debug(f"Getting default LLM config for profile: '{selected_llm_name}'")
|
192
|
+
llm_profiles = config_dict.get("llm", {})
|
193
|
+
if not isinstance(llm_profiles, dict): raise ValueError("'llm' section missing or invalid.")
|
194
|
+
llm_config = llm_profiles.get(selected_llm_name)
|
195
|
+
if not llm_config:
|
196
|
+
if selected_llm_name != "default" and "default" in llm_profiles:
|
197
|
+
logger.warning(f"Profile '{selected_llm_name}' not found, falling back to 'default'.")
|
198
|
+
llm_config = llm_profiles.get("default")
|
199
|
+
if not llm_config: # Guard against empty 'default'
|
200
|
+
raise ValueError(f"LLM profile '{selected_llm_name}' not found and 'default' profile is missing or invalid.")
|
201
|
+
else: raise ValueError(f"LLM profile '{selected_llm_name}' (nor 'default') not found.")
|
202
|
+
if not isinstance(llm_config, dict): raise ValueError(f"LLM profile '{selected_llm_name}' invalid.")
|
203
|
+
if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Using LLM profile '{selected_llm_name}': {redact_sensitive_data(llm_config)}")
|
204
|
+
return llm_config
|
205
|
+
|
206
|
+
def validate_api_keys(config_dict: Dict[str, Any], selected_llm: str = "default") -> Dict[str, Any]:
|
207
|
+
"""Validates API key presence for a selected LLM profile (called by load_llm_config)."""
|
208
|
+
logger.debug(f"Validating API keys for LLM profile '{selected_llm}'.")
|
209
|
+
llm_profiles = config_dict.get("llm", {})
|
210
|
+
if not isinstance(llm_profiles, dict): logger.warning("No 'llm' section found, skipping API key validation."); return config_dict
|
211
|
+
llm_config = llm_profiles.get(selected_llm)
|
212
|
+
if not isinstance(llm_config, dict): logger.warning(f"No config for LLM profile '{selected_llm}', skipping validation."); return config_dict
|
213
|
+
|
214
|
+
api_key_required = llm_config.get("api_key_required", True)
|
215
|
+
api_key = llm_config.get("api_key")
|
216
|
+
# Use the fact that resolve_placeholders now returns None for missing env vars
|
217
|
+
key_is_missing_or_empty = api_key is None or (isinstance(api_key, str) and not api_key.strip())
|
218
|
+
|
219
|
+
if api_key_required and key_is_missing_or_empty:
|
220
|
+
# If the key is missing/empty *after* resolving placeholders, it means
|
221
|
+
# neither the config nor the specific env var had it.
|
222
|
+
# Check OPENAI_API_KEY as a general fallback ONLY IF not found specifically.
|
223
|
+
common_fallback_var = "OPENAI_API_KEY"
|
224
|
+
fallback_key = os.getenv(common_fallback_var)
|
225
|
+
|
226
|
+
specific_env_var_name = f"{selected_llm.upper()}_API_KEY" # e.g., OPENAI_API_KEY, ANTHROPIC_API_KEY
|
227
|
+
|
228
|
+
# Check specific env var first
|
229
|
+
specific_key = os.getenv(specific_env_var_name)
|
230
|
+
if specific_key:
|
231
|
+
logger.info(f"API key missing/empty in resolved config for '{selected_llm}', using env var '{specific_env_var_name}'.")
|
232
|
+
# Update the config dict in place (or return a modified copy if preferred)
|
233
|
+
llm_config["api_key"] = specific_key
|
234
|
+
elif fallback_key:
|
235
|
+
logger.info(f"API key missing/empty for '{selected_llm}' and specific env var '{specific_env_var_name}' not set. Using fallback env var '{common_fallback_var}'.")
|
236
|
+
llm_config["api_key"] = fallback_key
|
237
|
+
else:
|
238
|
+
raise ValueError(f"Required API key for LLM profile '{selected_llm}' is missing or empty. Checked config, env var '{specific_env_var_name}', and fallback '{common_fallback_var}'.")
|
239
|
+
|
240
|
+
elif api_key_required: logger.debug(f"API key validation successful for '{selected_llm}'.")
|
241
|
+
else: logger.debug(f"API key not required for '{selected_llm}'.")
|
242
|
+
# Return the potentially modified config_dict (or just llm_config part if preferred)
|
243
|
+
return config_dict
|
244
|
+
|
245
|
+
|
246
|
+
def validate_and_select_llm_provider(config_dict: Dict[str, Any]) -> Dict[str, Any]:
|
247
|
+
"""Validates the selected LLM provider and returns its config."""
|
248
|
+
logger.debug("Validating and selecting LLM provider based on DEFAULT_LLM.")
|
109
249
|
try:
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
250
|
+
llm_name = os.getenv("DEFAULT_LLM", "default")
|
251
|
+
llm_config = load_llm_config(config_dict, llm_name) # Use load_llm_config which includes validation
|
252
|
+
logger.debug(f"LLM provider '{llm_name}' validated successfully.")
|
253
|
+
return llm_config
|
254
|
+
except ValueError as e: logger.error(f"LLM provider validation failed: {e}"); raise
|
255
|
+
|
256
|
+
def inject_env_vars(config_dict: Dict[str, Any]) -> Dict[str, Any]:
|
257
|
+
"""Ensures placeholders are resolved (delegates to resolve_placeholders)."""
|
258
|
+
logger.debug("Ensuring environment variable placeholders are resolved.")
|
259
|
+
return resolve_placeholders(config_dict)
|
260
|
+
|
261
|
+
def load_llm_config(config_dict: Optional[Dict[str, Any]] = None, llm_name: Optional[str] = None) -> Dict[str, Any]:
|
262
|
+
"""Loads and validates config for a specific LLM profile."""
|
263
|
+
if config_dict is None:
|
264
|
+
# Try loading from global if not provided
|
265
|
+
global_config = globals().get("config")
|
266
|
+
if not global_config:
|
267
|
+
try: config_dict = load_server_config(); globals()["config"] = config_dict
|
268
|
+
except Exception as e: raise ValueError("Global config not loaded and no config_dict provided.") from e
|
269
|
+
else:
|
270
|
+
config_dict = global_config
|
271
|
+
|
272
|
+
target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
|
273
|
+
logger.debug(f"Loading LLM config for profile: '{target_llm_name}'")
|
274
|
+
# Resolve placeholders FIRST using the provided or loaded config_dict
|
275
|
+
resolved_config = resolve_placeholders(config_dict)
|
276
|
+
|
277
|
+
llm_profiles = resolved_config.get("llm", {})
|
278
|
+
if not isinstance(llm_profiles, dict): raise ValueError("'llm' section must be a dictionary.")
|
279
|
+
llm_config = llm_profiles.get(target_llm_name)
|
280
|
+
|
281
|
+
# Fallback Logic (if profile not found after resolving)
|
282
|
+
if not llm_config:
|
283
|
+
logger.warning(f"LLM config for '{target_llm_name}' not found. Generating fallback.")
|
284
|
+
fb_provider = os.getenv("DEFAULT_LLM_PROVIDER", "openai"); fb_model = os.getenv("DEFAULT_LLM_MODEL", "gpt-4o")
|
285
|
+
# Check env vars for fallback API key *after* trying the specific one based on target_llm_name
|
286
|
+
specific_env_key = os.getenv(f"{target_llm_name.upper()}_API_KEY")
|
287
|
+
openai_env_key = os.getenv("OPENAI_API_KEY")
|
288
|
+
fb_api_key = specific_env_key or openai_env_key or "" # Use specific, then openai, then empty
|
289
|
+
|
290
|
+
specific_env_url = os.getenv(f"{target_llm_name.upper()}_BASE_URL")
|
291
|
+
openai_env_url = os.getenv("OPENAI_API_BASE")
|
292
|
+
default_openai_url = "https://api.openai.com/v1" if fb_provider == "openai" else None
|
293
|
+
fb_base_url = specific_env_url or openai_env_url or default_openai_url
|
294
|
+
|
295
|
+
llm_config = {k: v for k, v in {
|
296
|
+
"provider": fb_provider,
|
297
|
+
"model": fb_model,
|
298
|
+
"base_url": fb_base_url,
|
299
|
+
"api_key": fb_api_key, # Use the determined fallback key
|
300
|
+
# Determine requirement based on provider (adjust providers as needed)
|
301
|
+
"api_key_required": fb_provider not in ["ollama", "lmstudio", "groq"] # Example: groq might need key
|
302
|
+
}.items() if v is not None}
|
303
|
+
logger.debug(f"Using fallback config for '{target_llm_name}': {redact_sensitive_data(llm_config)}")
|
304
|
+
|
305
|
+
if not isinstance(llm_config, dict): raise ValueError(f"LLM profile '{target_llm_name}' must be a dictionary.")
|
306
|
+
|
307
|
+
# --- API Key Validation integrated here ---
|
308
|
+
api_key_required = llm_config.get("api_key_required", True)
|
309
|
+
# Check the api_key *within the potentially generated or loaded llm_config*
|
310
|
+
api_key = llm_config.get("api_key")
|
311
|
+
key_is_missing_or_empty = api_key is None or (isinstance(api_key, str) and not api_key.strip())
|
312
|
+
|
313
|
+
if api_key_required and key_is_missing_or_empty:
|
314
|
+
# Key is missing/empty after config resolution and fallback generation.
|
315
|
+
# Re-check environment variables as a final step before erroring.
|
316
|
+
specific_env_var_name = f"{target_llm_name.upper()}_API_KEY"
|
317
|
+
common_fallback_var = "OPENAI_API_KEY"
|
318
|
+
specific_key_from_env = os.getenv(specific_env_var_name)
|
319
|
+
fallback_key_from_env = os.getenv(common_fallback_var)
|
320
|
+
|
321
|
+
if specific_key_from_env:
|
322
|
+
logger.info(f"API key missing/empty in config/fallback for '{target_llm_name}', using env var '{specific_env_var_name}'.")
|
323
|
+
llm_config["api_key"] = specific_key_from_env # Update config with key from env
|
324
|
+
elif fallback_key_from_env:
|
325
|
+
logger.info(f"API key missing/empty for '{target_llm_name}' and specific env var '{specific_env_var_name}' not set. Using fallback env var '{common_fallback_var}'.")
|
326
|
+
llm_config["api_key"] = fallback_key_from_env # Update config with key from env
|
327
|
+
else:
|
328
|
+
# If still missing after checking env vars again, raise error
|
329
|
+
raise ValueError(f"Required API key for LLM profile '{target_llm_name}' is missing or empty. Checked config, fallback generation, env var '{specific_env_var_name}', and fallback '{common_fallback_var}'.")
|
330
|
+
|
331
|
+
# Log final config being used
|
332
|
+
if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Final loaded config for '{target_llm_name}': {redact_sensitive_data(llm_config)}")
|
333
|
+
return llm_config
|
334
|
+
|
335
|
+
|
336
|
+
def get_llm_model(config_dict: Dict[str, Any], llm_name: Optional[str] = None) -> str:
|
337
|
+
"""Retrieves the 'model' name string for a specific LLM profile."""
|
338
|
+
target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
|
339
|
+
try: llm_config = load_llm_config(config_dict, target_llm_name)
|
340
|
+
except ValueError as e: raise ValueError(f"Could not load config for LLM '{target_llm_name}': {e}") from e
|
341
|
+
model_name = llm_config.get("model")
|
342
|
+
if not model_name or not isinstance(model_name, str): raise ValueError(f"'model' name missing/invalid for LLM '{target_llm_name}'.")
|
343
|
+
logger.debug(f"Retrieved model name '{model_name}' for LLM '{target_llm_name}'")
|
344
|
+
return model_name
|
345
|
+
|
346
|
+
def load_and_validate_llm(config_dict: Dict[str, Any], llm_name: Optional[str] = None) -> Dict[str, Any]:
|
347
|
+
"""Loads and validates config for a specific LLM (wrapper for load_llm_config)."""
|
348
|
+
target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
|
349
|
+
logger.debug(f"Loading and validating LLM (via load_llm_config) for profile: {target_llm_name}")
|
350
|
+
return load_llm_config(config_dict, target_llm_name)
|
351
|
+
|
352
|
+
# --- End of Missing Functions ---
|