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.
Files changed (307) hide show
  1. open_swarm-0.1.1748636295.dist-info/METADATA +257 -0
  2. open_swarm-0.1.1748636295.dist-info/RECORD +89 -0
  3. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/WHEEL +2 -1
  4. open_swarm-0.1.1748636295.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1748636295.dist-info/top_level.txt +1 -0
  6. swarm/__init__.py +2 -0
  7. swarm/agent/agent.py +49 -0
  8. swarm/auth.py +48 -113
  9. swarm/consumers.py +0 -19
  10. swarm/core.py +411 -0
  11. swarm/extensions/blueprint/__init__.py +16 -30
  12. swarm/extensions/blueprint/agent_utils.py +45 -0
  13. swarm/extensions/blueprint/blueprint_base.py +562 -0
  14. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  15. swarm/extensions/blueprint/django_utils.py +79 -181
  16. swarm/extensions/blueprint/interactive_mode.py +72 -67
  17. swarm/extensions/blueprint/output_utils.py +82 -0
  18. swarm/{core → extensions/blueprint}/spinner.py +21 -30
  19. swarm/extensions/cli/cli_args.py +0 -6
  20. swarm/extensions/cli/commands/blueprint_management.py +9 -47
  21. swarm/extensions/cli/commands/config_management.py +6 -5
  22. swarm/extensions/cli/commands/edit_config.py +7 -16
  23. swarm/extensions/cli/commands/list_blueprints.py +1 -1
  24. swarm/extensions/cli/commands/validate_env.py +4 -11
  25. swarm/extensions/cli/commands/validate_envvars.py +6 -6
  26. swarm/extensions/cli/interactive_shell.py +2 -16
  27. swarm/extensions/config/config_loader.py +345 -107
  28. swarm/{core → extensions/config}/config_manager.py +38 -50
  29. swarm/{core → extensions/config}/server_config.py +0 -32
  30. swarm/extensions/launchers/build_launchers.py +14 -0
  31. swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
  32. swarm/extensions/launchers/swarm_api.py +64 -8
  33. swarm/extensions/launchers/swarm_cli.py +300 -8
  34. swarm/extensions/mcp/__init__.py +1 -0
  35. swarm/extensions/mcp/cache_utils.py +32 -0
  36. swarm/extensions/mcp/mcp_client.py +233 -0
  37. swarm/extensions/mcp/mcp_tool_provider.py +135 -0
  38. swarm/extensions/mcp/mcp_utils.py +260 -0
  39. swarm/llm/chat_completion.py +166 -0
  40. swarm/serializers.py +5 -96
  41. swarm/settings.py +133 -85
  42. swarm/types.py +91 -0
  43. swarm/urls.py +74 -57
  44. swarm/utils/context_utils.py +4 -10
  45. swarm/utils/general_utils.py +0 -21
  46. swarm/utils/redact.py +36 -23
  47. swarm/views/api_views.py +39 -48
  48. swarm/views/chat_views.py +76 -236
  49. swarm/views/core_views.py +87 -80
  50. swarm/views/model_views.py +121 -64
  51. swarm/views/utils.py +439 -65
  52. swarm/views/web_views.py +2 -2
  53. open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
  54. open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
  55. open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
  56. swarm/blueprints/README.md +0 -68
  57. swarm/blueprints/blueprint_audit_status.json +0 -27
  58. swarm/blueprints/chatbot/README.md +0 -40
  59. swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
  60. swarm/blueprints/chatbot/metadata.json +0 -23
  61. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
  62. swarm/blueprints/chucks_angels/README.md +0 -11
  63. swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
  64. swarm/blueprints/chucks_angels/test_basic.py +0 -3
  65. swarm/blueprints/codey/CODEY.md +0 -15
  66. swarm/blueprints/codey/README.md +0 -115
  67. swarm/blueprints/codey/blueprint_codey.py +0 -1072
  68. swarm/blueprints/codey/codey_cli.py +0 -373
  69. swarm/blueprints/codey/instructions.md +0 -17
  70. swarm/blueprints/codey/metadata.json +0 -23
  71. swarm/blueprints/common/operation_box_utils.py +0 -83
  72. swarm/blueprints/digitalbutlers/README.md +0 -11
  73. swarm/blueprints/digitalbutlers/__init__.py +0 -1
  74. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
  75. swarm/blueprints/digitalbutlers/test_basic.py +0 -3
  76. swarm/blueprints/divine_code/README.md +0 -3
  77. swarm/blueprints/divine_code/__init__.py +0 -10
  78. swarm/blueprints/divine_code/apps.py +0 -11
  79. swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
  80. swarm/blueprints/django_chat/apps.py +0 -6
  81. swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
  82. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
  83. swarm/blueprints/django_chat/urls.py +0 -8
  84. swarm/blueprints/django_chat/views.py +0 -32
  85. swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
  86. swarm/blueprints/flock/README.md +0 -11
  87. swarm/blueprints/flock/__init__.py +0 -8
  88. swarm/blueprints/flock/blueprint_flock.py +0 -7
  89. swarm/blueprints/flock/test_basic.py +0 -3
  90. swarm/blueprints/geese/README.md +0 -10
  91. swarm/blueprints/geese/__init__.py +0 -8
  92. swarm/blueprints/geese/blueprint_geese.py +0 -384
  93. swarm/blueprints/geese/geese_cli.py +0 -102
  94. swarm/blueprints/jeeves/README.md +0 -41
  95. swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
  96. swarm/blueprints/jeeves/jeeves_cli.py +0 -55
  97. swarm/blueprints/jeeves/metadata.json +0 -24
  98. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
  99. swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
  100. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
  101. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
  102. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
  103. swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
  104. swarm/blueprints/poets/blueprint_poets.py +0 -546
  105. swarm/blueprints/poets/poets_cli.py +0 -23
  106. swarm/blueprints/rue_code/README.md +0 -8
  107. swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
  108. swarm/blueprints/rue_code/rue_code_cli.py +0 -43
  109. swarm/blueprints/stewie/apps.py +0 -12
  110. swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
  111. swarm/blueprints/stewie/models.py +0 -19
  112. swarm/blueprints/stewie/serializers.py +0 -10
  113. swarm/blueprints/stewie/settings.py +0 -17
  114. swarm/blueprints/stewie/urls.py +0 -11
  115. swarm/blueprints/stewie/views.py +0 -26
  116. swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
  117. swarm/blueprints/whinge_surf/README.md +0 -22
  118. swarm/blueprints/whinge_surf/__init__.py +0 -1
  119. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
  120. swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
  121. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  122. swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
  123. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
  124. swarm/blueprints/zeus/__init__.py +0 -2
  125. swarm/blueprints/zeus/apps.py +0 -4
  126. swarm/blueprints/zeus/blueprint_zeus.py +0 -270
  127. swarm/blueprints/zeus/zeus_cli.py +0 -13
  128. swarm/cli/async_input.py +0 -65
  129. swarm/cli/async_input_demo.py +0 -32
  130. swarm/core/agent_utils.py +0 -21
  131. swarm/core/blueprint_base.py +0 -769
  132. swarm/core/blueprint_discovery.py +0 -125
  133. swarm/core/blueprint_runner.py +0 -59
  134. swarm/core/blueprint_ux.py +0 -109
  135. swarm/core/build_launchers.py +0 -15
  136. swarm/core/cli/__init__.py +0 -1
  137. swarm/core/cli/commands/__init__.py +0 -1
  138. swarm/core/cli/commands/blueprint_management.py +0 -7
  139. swarm/core/cli/interactive_shell.py +0 -14
  140. swarm/core/cli/main.py +0 -50
  141. swarm/core/cli/utils/__init__.py +0 -1
  142. swarm/core/cli/utils/discover_commands.py +0 -18
  143. swarm/core/config_loader.py +0 -122
  144. swarm/core/output_utils.py +0 -193
  145. swarm/core/session_logger.py +0 -42
  146. swarm/core/slash_commands.py +0 -89
  147. swarm/core/swarm_api.py +0 -68
  148. swarm/core/swarm_cli.py +0 -216
  149. swarm/core/utils/__init__.py +0 -0
  150. swarm/extensions/blueprint/cli_handler.py +0 -197
  151. swarm/extensions/blueprint/runnable_blueprint.py +0 -42
  152. swarm/extensions/cli/utils/__init__.py +0 -1
  153. swarm/extensions/cli/utils/async_input.py +0 -46
  154. swarm/extensions/cli/utils/prompt_user.py +0 -3
  155. swarm/management/__init__.py +0 -0
  156. swarm/management/commands/__init__.py +0 -0
  157. swarm/management/commands/runserver.py +0 -58
  158. swarm/middleware.py +0 -65
  159. swarm/permissions.py +0 -38
  160. swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
  161. swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
  162. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
  163. swarm/static/contrib/markedjs/marked.min.js +0 -6
  164. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
  165. swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
  166. swarm/static/contrib/tabler-icons/archive.svg +0 -21
  167. swarm/static/contrib/tabler-icons/artboard.svg +0 -27
  168. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
  169. swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
  170. swarm/static/contrib/tabler-icons/carambola.svg +0 -19
  171. swarm/static/contrib/tabler-icons/copy.svg +0 -20
  172. swarm/static/contrib/tabler-icons/download.svg +0 -21
  173. swarm/static/contrib/tabler-icons/edit.svg +0 -21
  174. swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
  175. swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
  176. swarm/static/contrib/tabler-icons/headset.svg +0 -22
  177. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
  178. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
  179. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
  180. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
  181. swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
  182. swarm/static/contrib/tabler-icons/message-star.svg +0 -22
  183. swarm/static/contrib/tabler-icons/message-x.svg +0 -23
  184. swarm/static/contrib/tabler-icons/message.svg +0 -21
  185. swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
  186. swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
  187. swarm/static/contrib/tabler-icons/robot.svg +0 -26
  188. swarm/static/contrib/tabler-icons/search.svg +0 -19
  189. swarm/static/contrib/tabler-icons/settings.svg +0 -20
  190. swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
  191. swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
  192. swarm/static/css/dropdown.css +0 -22
  193. swarm/static/htmx/htmx.min.js +0 -0
  194. swarm/static/js/dropdown.js +0 -23
  195. swarm/static/rest_mode/css/base.css +0 -470
  196. swarm/static/rest_mode/css/chat-history.css +0 -286
  197. swarm/static/rest_mode/css/chat.css +0 -251
  198. swarm/static/rest_mode/css/chatbot.css +0 -74
  199. swarm/static/rest_mode/css/chatgpt.css +0 -62
  200. swarm/static/rest_mode/css/colors/corporate.css +0 -74
  201. swarm/static/rest_mode/css/colors/pastel.css +0 -81
  202. swarm/static/rest_mode/css/colors/tropical.css +0 -82
  203. swarm/static/rest_mode/css/general.css +0 -142
  204. swarm/static/rest_mode/css/layout.css +0 -167
  205. swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
  206. swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
  207. swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
  208. swarm/static/rest_mode/css/messages.css +0 -84
  209. swarm/static/rest_mode/css/messenger.css +0 -135
  210. swarm/static/rest_mode/css/settings.css +0 -91
  211. swarm/static/rest_mode/css/simple.css +0 -44
  212. swarm/static/rest_mode/css/slack.css +0 -58
  213. swarm/static/rest_mode/css/style.css +0 -156
  214. swarm/static/rest_mode/css/theme.css +0 -30
  215. swarm/static/rest_mode/css/toast.css +0 -40
  216. swarm/static/rest_mode/js/auth.js +0 -9
  217. swarm/static/rest_mode/js/blueprint.js +0 -41
  218. swarm/static/rest_mode/js/blueprintUtils.js +0 -12
  219. swarm/static/rest_mode/js/chatLogic.js +0 -79
  220. swarm/static/rest_mode/js/debug.js +0 -63
  221. swarm/static/rest_mode/js/events.js +0 -98
  222. swarm/static/rest_mode/js/main.js +0 -19
  223. swarm/static/rest_mode/js/messages.js +0 -264
  224. swarm/static/rest_mode/js/messengerLogic.js +0 -355
  225. swarm/static/rest_mode/js/modules/apiService.js +0 -84
  226. swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
  227. swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
  228. swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
  229. swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
  230. swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
  231. swarm/static/rest_mode/js/modules/state.js +0 -7
  232. swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
  233. swarm/static/rest_mode/js/modules/validation.js +0 -23
  234. swarm/static/rest_mode/js/rendering.js +0 -119
  235. swarm/static/rest_mode/js/settings.js +0 -130
  236. swarm/static/rest_mode/js/sidebar.js +0 -94
  237. swarm/static/rest_mode/js/simpleLogic.js +0 -37
  238. swarm/static/rest_mode/js/slackLogic.js +0 -66
  239. swarm/static/rest_mode/js/splash.js +0 -76
  240. swarm/static/rest_mode/js/theme.js +0 -111
  241. swarm/static/rest_mode/js/toast.js +0 -36
  242. swarm/static/rest_mode/js/ui.js +0 -265
  243. swarm/static/rest_mode/js/validation.js +0 -57
  244. swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
  245. swarm/static/rest_mode/svg/arrow_down.svg +0 -5
  246. swarm/static/rest_mode/svg/arrow_left.svg +0 -5
  247. swarm/static/rest_mode/svg/arrow_right.svg +0 -5
  248. swarm/static/rest_mode/svg/arrow_up.svg +0 -5
  249. swarm/static/rest_mode/svg/attach.svg +0 -8
  250. swarm/static/rest_mode/svg/avatar.svg +0 -7
  251. swarm/static/rest_mode/svg/canvas.svg +0 -6
  252. swarm/static/rest_mode/svg/chat_history.svg +0 -4
  253. swarm/static/rest_mode/svg/close.svg +0 -5
  254. swarm/static/rest_mode/svg/copy.svg +0 -4
  255. swarm/static/rest_mode/svg/dark_mode.svg +0 -3
  256. swarm/static/rest_mode/svg/edit.svg +0 -5
  257. swarm/static/rest_mode/svg/layout.svg +0 -9
  258. swarm/static/rest_mode/svg/logo.svg +0 -29
  259. swarm/static/rest_mode/svg/logout.svg +0 -5
  260. swarm/static/rest_mode/svg/mobile.svg +0 -5
  261. swarm/static/rest_mode/svg/new_chat.svg +0 -4
  262. swarm/static/rest_mode/svg/not_visible.svg +0 -5
  263. swarm/static/rest_mode/svg/plus.svg +0 -7
  264. swarm/static/rest_mode/svg/run_code.svg +0 -6
  265. swarm/static/rest_mode/svg/save.svg +0 -4
  266. swarm/static/rest_mode/svg/search.svg +0 -6
  267. swarm/static/rest_mode/svg/settings.svg +0 -4
  268. swarm/static/rest_mode/svg/speaker.svg +0 -5
  269. swarm/static/rest_mode/svg/stop.svg +0 -6
  270. swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
  271. swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
  272. swarm/static/rest_mode/svg/toggle_off.svg +0 -6
  273. swarm/static/rest_mode/svg/toggle_on.svg +0 -6
  274. swarm/static/rest_mode/svg/trash.svg +0 -10
  275. swarm/static/rest_mode/svg/undo.svg +0 -3
  276. swarm/static/rest_mode/svg/visible.svg +0 -8
  277. swarm/static/rest_mode/svg/voice.svg +0 -10
  278. swarm/templates/account/login.html +0 -22
  279. swarm/templates/account/signup.html +0 -32
  280. swarm/templates/base.html +0 -30
  281. swarm/templates/chat.html +0 -43
  282. swarm/templates/index.html +0 -35
  283. swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
  284. swarm/templates/rest_mode/components/header.html +0 -45
  285. swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
  286. swarm/templates/rest_mode/components/settings_dialog.html +0 -97
  287. swarm/templates/rest_mode/components/splash_screen.html +0 -7
  288. swarm/templates/rest_mode/components/top_bar.html +0 -28
  289. swarm/templates/rest_mode/message_ui.html +0 -50
  290. swarm/templates/rest_mode/slackbot.html +0 -30
  291. swarm/templates/simple_blueprint_page.html +0 -24
  292. swarm/templates/websocket_partials/final_system_message.html +0 -3
  293. swarm/templates/websocket_partials/system_message.html +0 -4
  294. swarm/templates/websocket_partials/user_message.html +0 -5
  295. swarm/utils/ansi_box.py +0 -34
  296. swarm/utils/disable_tracing.py +0 -38
  297. swarm/utils/log_utils.py +0 -63
  298. swarm/utils/openai_patch.py +0 -33
  299. swarm/ux/ansi_box.py +0 -43
  300. swarm/ux/spinner.py +0 -53
  301. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/licenses/LICENSE +0 -0
  302. /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
  303. /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
  304. /swarm/{core → extensions/config}/setup_wizard.py +0 -0
  305. /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
  306. /swarm/{core → extensions/config}/utils/logger.py +0 -0
  307. /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
swarm/views/utils.py CHANGED
@@ -1,81 +1,455 @@
1
+ """
2
+ Utility functions for Swarm views.
3
+ """
4
+ import json
5
+ import uuid
6
+ import time
7
+ import os
8
+ import redis
1
9
  import logging
10
+ from typing import Any, Dict, List, Optional, Tuple
11
+ from pathlib import Path
12
+
2
13
  from django.conf import settings
3
- from asgiref.sync import sync_to_async, async_to_sync
14
+ from rest_framework.response import Response
15
+ from rest_framework import status
16
+
17
+ # Project-specific imports
18
+ from swarm.models import ChatConversation, ChatMessage
19
+ from swarm.extensions.blueprint import discover_blueprints
20
+ from swarm.extensions.blueprint.blueprint_base import BlueprintBase
21
+ from swarm.extensions.config.config_loader import load_server_config, load_llm_config
22
+ from swarm.utils.logger_setup import setup_logger
23
+ from swarm.utils.redact import redact_sensitive_data
24
+ from swarm.utils.general_utils import extract_chat_id
25
+ from swarm.extensions.blueprint.blueprint_utils import filter_blueprints
26
+ from swarm.settings import BASE_DIR, BLUEPRINTS_DIR # Import BLUEPRINTS_DIR
27
+
28
+ logger = setup_logger(__name__)
29
+
30
+ # --- Configuration Loading ---
31
+ CONFIG_PATH = Path(settings.BASE_DIR) / 'swarm_config.json'
32
+ config = {}
33
+ llm_config = {}
34
+ llm_model = "default"
35
+ llm_provider = "unknown"
36
+
37
+ def load_configs():
38
+ """Load main and LLM configurations."""
39
+ global config, llm_config, llm_model, llm_provider
40
+ try:
41
+ config = load_server_config(str(CONFIG_PATH))
42
+ logger.info(f"Server config loaded from {CONFIG_PATH}")
43
+ except FileNotFoundError:
44
+ logger.warning(f"Configuration file not found at {CONFIG_PATH}. Using defaults.")
45
+ config = {} # Use empty dict or default structure if needed
46
+ except ValueError as e:
47
+ logger.error(f"Error loading server config: {e}. Using defaults.")
48
+ config = {}
49
+ except Exception as e:
50
+ logger.critical(f"Unexpected error loading server config: {e}", exc_info=True)
51
+ config = {} # Critical error, use empty config
52
+
53
+ try:
54
+ llm_config = load_llm_config(config) # Load default LLM config
55
+ llm_model = llm_config.get("model", "default")
56
+ llm_provider = llm_config.get("provider", "unknown")
57
+ logger.info(f"Default LLM config loaded: Provider={llm_provider}, Model={llm_model}")
58
+ except ValueError as e:
59
+ logger.error(f"Failed to load default LLM configuration: {e}. LLM features may fail.")
60
+ llm_config = {} # Ensure llm_config is a dict even on error
61
+ llm_model = "error"
62
+ llm_provider = "error"
63
+ except Exception as e:
64
+ logger.critical(f"Unexpected error loading LLM config: {e}", exc_info=True)
65
+ llm_config = {}
66
+ llm_model = "error"
67
+ llm_provider = "error"
68
+
69
+ load_configs() # Load configs when module is imported
70
+
71
+ # --- Blueprint Discovery ---
72
+ blueprints_metadata = {}
73
+ def discover_and_load_blueprints():
74
+ """Discover blueprints from the configured directory."""
75
+ global blueprints_metadata
76
+ try:
77
+ # Ensure BLUEPRINTS_DIR is a Path object
78
+ bp_dir_path = Path(BLUEPRINTS_DIR) if isinstance(BLUEPRINTS_DIR, str) else BLUEPRINTS_DIR
79
+ if not bp_dir_path.is_absolute():
80
+ bp_dir_path = Path(settings.BASE_DIR).parent / bp_dir_path # Assuming relative to project root
81
+ logger.info(f"Discovering blueprints in: {bp_dir_path}")
82
+ discovered = discover_blueprints(directories=[str(bp_dir_path)])
83
+ blueprints_metadata = discovered
84
+ loaded_names = list(blueprints_metadata.keys())
85
+ logger.info(f"Discovered blueprints: {loaded_names if loaded_names else 'None'}")
86
+ except FileNotFoundError:
87
+ logger.warning(f"Blueprints directory '{BLUEPRINTS_DIR}' not found. No blueprints discovered dynamically.")
88
+ blueprints_metadata = {}
89
+ except Exception as e:
90
+ logger.error(f"Failed during blueprint discovery: {e}", exc_info=True)
91
+ blueprints_metadata = {}
92
+
93
+ discover_and_load_blueprints() # Discover blueprints when module is imported
94
+
95
+ # --- Redis Client Initialization ---
96
+ REDIS_AVAILABLE = bool(os.getenv("STATEFUL_CHAT_ID_PATH")) and hasattr(settings, 'REDIS_HOST') and hasattr(settings, 'REDIS_PORT')
97
+ redis_client = None
98
+ if REDIS_AVAILABLE:
99
+ try:
100
+ redis_client = redis.Redis(
101
+ host=settings.REDIS_HOST,
102
+ port=settings.REDIS_PORT,
103
+ decode_responses=True
104
+ )
105
+ redis_client.ping()
106
+ logger.info(f"Redis connection successful ({settings.REDIS_HOST}:{settings.REDIS_PORT}).")
107
+ except Exception as e:
108
+ logger.warning(f"Redis connection failed: {e}. Stateful chat history via Redis is disabled.")
109
+ REDIS_AVAILABLE = False
110
+ else:
111
+ logger.info("Redis configuration not found or STATEFUL_CHAT_ID_PATH not set. Stateful chat history via Redis is disabled.")
112
+
113
+ # --- Helper Functions ---
114
+
115
+ def serialize_swarm_response(response: Any, model_name: str, context_variables: Dict[str, Any]) -> Dict[str, Any]:
116
+ """Serialize a blueprint response into an OpenAI-compatible chat completion format."""
117
+ logger.debug(f"Serializing Swarm response, type: {type(response)}, model: {model_name}")
118
+
119
+ # Determine messages from response
120
+ if hasattr(response, 'messages') and isinstance(response.messages, list):
121
+ messages = response.messages
122
+ elif isinstance(response, dict) and isinstance(response.get("messages"), list):
123
+ messages = response.get("messages", [])
124
+ elif isinstance(response, str):
125
+ logger.warning(f"Received raw string response, wrapping as assistant message: {response[:100]}...")
126
+ messages = [{"role": "assistant", "content": response}]
127
+ else:
128
+ logger.error(f"Unexpected response type for serialization: {type(response)}. Returning empty response.")
129
+ messages = []
130
+
131
+ # Create choices array based on assistant messages with content
132
+ choices = []
133
+ for i, msg in enumerate(messages):
134
+ if isinstance(msg, dict) and msg.get("role") == "assistant" and msg.get("content") is not None:
135
+ choice = {
136
+ "index": len(choices),
137
+ "message": {
138
+ "role": "assistant",
139
+ "content": msg["content"]
140
+ },
141
+ "finish_reason": "stop" # Assume stop for non-streaming
142
+ }
143
+ # Include tool_calls if present in the original message
144
+ if msg.get("tool_calls") is not None:
145
+ choice["message"]["tool_calls"] = msg["tool_calls"]
146
+ choice["finish_reason"] = "tool_calls" # Adjust finish reason if tools are called
147
+
148
+ choices.append(choice)
149
+ logger.debug(f"Added choice {len(choices)-1}: role={choice['message']['role']}, finish={choice['finish_reason']}")
150
+
151
+ if not choices and messages:
152
+ # Fallback if no assistant message with content, maybe use last message?
153
+ logger.warning("No assistant messages with content found for 'choices'. Using last message if possible.")
154
+ # This part might need refinement based on expected behavior for tool-only responses
155
+
156
+ # Estimate token usage (basic approximation)
157
+ prompt_tokens = 0
158
+ completion_tokens = 0
159
+ total_tokens = 0
160
+ # Need access to the *input* messages for prompt_tokens, which aren't passed here.
161
+ # This usage calculation will be inaccurate without the original prompt.
162
+ for msg in messages: # Calculating based on response messages only
163
+ if isinstance(msg, dict):
164
+ content_tokens = len(str(msg.get("content", "")).split())
165
+ if msg.get("role") == "assistant":
166
+ completion_tokens += content_tokens
167
+ total_tokens += content_tokens
168
+ if msg.get("tool_calls"): # Add rough estimate for tool call overhead
169
+ total_tokens += len(json.dumps(msg["tool_calls"])) // 4
170
+
171
+ logger.warning("Token usage calculation is approximate and based only on response messages.")
172
+
173
+ # Basic serialization structure
174
+ serialized_response = {
175
+ "id": f"swarm-chat-{uuid.uuid4()}",
176
+ "object": "chat.completion",
177
+ "created": int(time.time()),
178
+ "model": model_name,
179
+ "choices": choices,
180
+ "usage": {
181
+ "prompt_tokens": prompt_tokens, # Inaccurate without input messages
182
+ "completion_tokens": completion_tokens,
183
+ "total_tokens": total_tokens # Inaccurate
184
+ },
185
+ # Optionally include context and full response for debugging/state
186
+ # "context_variables": context_variables,
187
+ # "full_response": response # Might contain non-serializable objects
188
+ }
189
+
190
+ logger.debug(f"Serialized response: id={serialized_response['id']}, choices={len(choices)}")
191
+ return serialized_response
192
+
193
+ def parse_chat_request(request: Any) -> Any:
194
+ """Parse incoming chat completion request body into components."""
195
+ try:
196
+ body = json.loads(request.body)
197
+ model = body.get("model", "default") # Default to 'default' if model not specified
198
+ messages = body.get("messages", [])
199
+
200
+ # Basic validation
201
+ if not isinstance(messages, list) or not messages:
202
+ # Try extracting single message if 'messages' is invalid/empty
203
+ if "message" in body:
204
+ single_msg = body["message"]
205
+ if isinstance(single_msg, str): messages = [{"role": "user", "content": single_msg}]
206
+ elif isinstance(single_msg, dict) and "content" in single_msg:
207
+ if "role" not in single_msg: single_msg["role"] = "user"
208
+ messages = [single_msg]
209
+ else:
210
+ return Response({"error": "'message' field is invalid."}, status=status.HTTP_400_BAD_REQUEST)
211
+ else:
212
+ return Response({"error": "'messages' field is required and must be a non-empty list."}, status=status.HTTP_400_BAD_REQUEST)
213
+
214
+ # Ensure all messages have a role (default to user if missing)
215
+ for msg in messages:
216
+ if not isinstance(msg, dict) or "content" not in msg:
217
+ # Allow tool calls without content
218
+ if not (isinstance(msg, dict) and msg.get("role") == "tool" and msg.get("tool_call_id")):
219
+ logger.error(f"Invalid message format found: {msg}")
220
+ return Response({"error": f"Invalid message format: {msg}"}, status=status.HTTP_400_BAD_REQUEST)
221
+ if "role" not in msg:
222
+ msg["role"] = "user"
223
+
224
+ context_variables = body.get("context_variables", {})
225
+ if not isinstance(context_variables, dict):
226
+ logger.warning("Invalid 'context_variables' format, using empty dict.")
227
+ context_variables = {}
228
+
229
+ conversation_id = extract_chat_id(body) or str(uuid.uuid4()) # Generate if not found/extracted
230
+
231
+ # Extract last tool_call_id for potential context filtering (optional)
232
+ tool_call_id = None
233
+ if messages and isinstance(messages[-1], dict) and messages[-1].get("role") == "tool":
234
+ tool_call_id = messages[-1].get("tool_call_id")
235
+
236
+ logger.debug(f"Parsed request: model={model}, messages_count={len(messages)}, context_keys={list(context_variables.keys())}, conv_id={conversation_id}, tool_id={tool_call_id}")
237
+ return (body, model, messages, context_variables, conversation_id, tool_call_id)
238
+
239
+ except json.JSONDecodeError:
240
+ logger.error("Invalid JSON payload received.")
241
+ return Response({"error": "Invalid JSON payload."}, status=status.HTTP_400_BAD_REQUEST)
242
+ except Exception as e:
243
+ logger.error(f"Error parsing request: {e}", exc_info=True)
244
+ return Response({"error": "Failed to parse request."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
245
+
246
+
247
+ def get_blueprint_instance(model: str, context_vars: dict) -> Any:
248
+ """Instantiate a blueprint instance based on the requested model."""
249
+ logger.debug(f"Attempting to get blueprint instance for model: {model}")
250
+ # Reload configs and discover blueprints on each request? Or rely on initial load?
251
+ # Let's rely on initial load for now, assuming blueprints don't change often.
252
+ # discover_and_load_blueprints() # Uncomment if dynamic reload is needed
253
+
254
+ blueprint_meta = blueprints_metadata.get(model)
255
+ if not blueprint_meta:
256
+ # Check if it's an LLM passthrough defined in config
257
+ llm_profile = config.get("llm", {}).get(model)
258
+ if llm_profile and llm_profile.get("passthrough"):
259
+ logger.warning(f"Model '{model}' is an LLM passthrough, not a blueprint. Returning None.")
260
+ # This scenario should ideally be handled before calling get_blueprint_instance
261
+ # Returning None might cause issues downstream. Consider raising an error
262
+ # or having the caller handle LLM passthrough separately.
263
+ # For now, returning None as a signal.
264
+ return None # Signal it's not a blueprint
265
+ else:
266
+ logger.error(f"Model '{model}' not found in discovered blueprints or LLM config.")
267
+ return Response({"error": f"Model '{model}' not found."}, status=status.HTTP_404_NOT_FOUND)
268
+
269
+ blueprint_class = blueprint_meta.get("blueprint_class")
270
+ if not blueprint_class or not issubclass(blueprint_class, BlueprintBase):
271
+ logger.error(f"Blueprint class for model '{model}' is invalid or not found.")
272
+ return Response({"error": f"Blueprint class for model '{model}' is invalid."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
273
+
274
+ try:
275
+ # Pass the initially loaded global 'config' to the blueprint instance
276
+ blueprint_instance = blueprint_class(config=config, debug=settings.DEBUG)
277
+ logger.info(f"Successfully instantiated blueprint: {model}")
278
+ # Optionally set active agent based on context, if blueprint supports it
279
+ # active_agent_name = context_vars.get("active_agent_name")
280
+ # if active_agent_name and hasattr(blueprint_instance, 'set_active_agent'):
281
+ # try:
282
+ # blueprint_instance.set_active_agent(active_agent_name)
283
+ # except ValueError as e:
284
+ # logger.warning(f"Could not set active agent '{active_agent_name}': {e}")
285
+ return blueprint_instance
286
+ except Exception as e:
287
+ logger.error(f"Error initializing blueprint '{model}': {e}", exc_info=True)
288
+ return Response({"error": f"Error initializing blueprint: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
289
+
290
+ def load_conversation_history(conversation_id: Optional[str], current_messages: List[dict], tool_call_id: Optional[str] = None) -> List[dict]:
291
+ """Load past messages for a conversation from Redis or database, combined with current."""
292
+ if not conversation_id:
293
+ logger.debug("No conversation_id provided, returning only current messages.")
294
+ return current_messages # Return only the messages from the current request
295
+
296
+ past_messages = []
297
+ # Try Redis first if available
298
+ if REDIS_AVAILABLE and redis_client:
299
+ try:
300
+ history_raw = redis_client.lrange(conversation_id, 0, -1) # Get all items in list
301
+ if history_raw:
302
+ # Redis stores list items as strings, parse each JSON string
303
+ past_messages = [json.loads(msg_str) for msg_str in history_raw]
304
+ logger.debug(f"Retrieved {len(past_messages)} messages from Redis list for conversation: {conversation_id}")
305
+ except redis.exceptions.RedisError as e:
306
+ logger.error(f"Redis error retrieving history for {conversation_id}: {e}", exc_info=True)
307
+ except json.JSONDecodeError as e:
308
+ logger.error(f"Error decoding JSON from Redis for {conversation_id}: {e}. History may be corrupted.")
309
+ # Potentially clear corrupted Redis key?
310
+ # redis_client.delete(conversation_id)
311
+ except Exception as e:
312
+ logger.error(f"Unexpected error retrieving from Redis for {conversation_id}: {e}", exc_info=True)
313
+
314
+ # Fallback to Database if Redis fails or history is empty
315
+ if not past_messages:
316
+ try:
317
+ conversation = ChatConversation.objects.filter(conversation_id=conversation_id).first()
318
+ if conversation:
319
+ # Query messages related to the conversation
320
+ query = conversation.messages.all().order_by("timestamp")
321
+ # Convert DB messages to the expected dict format
322
+ past_messages = [
323
+ {"role": msg.sender, "content": msg.content, "tool_calls": json.loads(msg.tool_calls) if msg.tool_calls else None}
324
+ for msg in query
325
+ ]
326
+ logger.debug(f"Retrieved {len(past_messages)} messages from DB for conversation: {conversation_id}")
327
+ else:
328
+ logger.debug(f"No existing conversation found in DB for ID: {conversation_id}")
329
+ past_messages = [] # Ensure it's an empty list if no conversation found
330
+ except Exception as e:
331
+ logger.error(f"Error retrieving conversation history from DB for {conversation_id}: {e}", exc_info=True)
332
+ past_messages = [] # Ensure empty list on error
333
+
334
+ # Combine history with current request messages
335
+ # Ensure roles are correct ('user' for human, 'assistant'/'tool' for AI/tools)
336
+ # Filter out potential duplicates if necessary (e.g., if client resends last user message)
337
+ combined_messages = past_messages + current_messages
338
+ logger.debug(f"Combined history: {len(past_messages)} past + {len(current_messages)} current = {len(combined_messages)} total messages for {conversation_id}")
339
+ return combined_messages
4
340
 
5
- # Assuming the discovery functions are correctly located now
6
- from swarm.core.blueprint_discovery import discover_blueprints
7
341
 
8
- logger = logging.getLogger(__name__)
342
+ def store_conversation_history(conversation_id: str, full_history: List[dict], response_obj: Optional[Any] = None):
343
+ """Store conversation history (including latest response) in DB and/or Redis."""
344
+ if not conversation_id:
345
+ logger.error("Cannot store history: conversation_id is missing.")
346
+ return False
9
347
 
10
- # --- Caching ---
11
- _blueprint_meta_cache = None # Cache for the {name: class} mapping
12
- _blueprint_instance_cache = {} # Simple instance cache for no-param blueprints
348
+ # Ensure response messages are included in the history to be stored
349
+ history_to_store = list(full_history) # Make a copy
350
+ if response_obj:
351
+ response_messages = []
352
+ if hasattr(response_obj, 'messages') and isinstance(response_obj.messages, list):
353
+ response_messages = response_obj.messages
354
+ elif isinstance(response_obj, dict) and isinstance(response_obj.get('messages'), list):
355
+ response_messages = response_obj.get('messages', [])
13
356
 
14
- # --- Blueprint Metadata Loading ---
15
- def _load_all_blueprint_metadata_sync():
16
- """Synchronous helper to perform blueprint discovery."""
17
- global _blueprint_meta_cache
18
- logger.info("Discovering blueprint classes (sync)...")
19
- blueprint_classes = discover_blueprints(settings.BLUEPRINT_DIRECTORY)
20
- logger.info(f"Found blueprint classes: {list(blueprint_classes.keys())}")
21
- _blueprint_meta_cache = blueprint_classes
22
- return blueprint_classes
357
+ # Add only messages not already in full_history (prevent duplicates if run_conversation includes input)
358
+ last_stored_content = json.dumps(history_to_store[-1]) if history_to_store else None
359
+ for msg in response_messages:
360
+ if json.dumps(msg) != last_stored_content:
361
+ history_to_store.append(msg)
23
362
 
24
- @sync_to_async
25
- def get_available_blueprints():
26
- """Asynchronously retrieves available blueprint classes."""
27
- global _blueprint_meta_cache
28
- if _blueprint_meta_cache is None:
29
- _load_all_blueprint_metadata_sync()
30
- return _blueprint_meta_cache
31
363
 
32
- # --- Blueprint Instance Loading ---
33
- # Removed _load_blueprint_class_sync
364
+ # --- Store in Database ---
365
+ try:
366
+ # Use update_or_create for conversation to handle creation/retrieval atomically
367
+ conversation, created = ChatConversation.objects.update_or_create(
368
+ conversation_id=conversation_id,
369
+ defaults={'user': None} # Add user association if available (e.g., from request.user)
370
+ )
371
+ if created:
372
+ logger.debug(f"Created new ChatConversation in DB: {conversation_id}")
373
+
374
+ # Efficiently store only the messages *added* in this turn (response messages)
375
+ # Assume `full_history` contains the prompt messages, and `response_messages` contains the response
376
+ messages_to_add_to_db = []
377
+ if response_obj:
378
+ response_msgs_from_obj = getattr(response_obj, 'messages', []) if hasattr(response_obj, 'messages') else response_obj.get('messages', []) if isinstance(response_obj, dict) else []
379
+ for msg in response_msgs_from_obj:
380
+ if isinstance(msg, dict):
381
+ # Basic validation
382
+ role = msg.get("role")
383
+ content = msg.get("content")
384
+ tool_calls = msg.get("tool_calls")
385
+ if role and (content is not None or tool_calls is not None):
386
+ messages_to_add_to_db.append(ChatMessage(
387
+ conversation=conversation,
388
+ sender=role,
389
+ content=content,
390
+ # Store tool_calls as JSON string
391
+ tool_calls=json.dumps(tool_calls) if tool_calls else None
392
+ ))
34
393
 
35
- async def get_blueprint_instance(blueprint_id: str, params: dict = None):
36
- """Asynchronously gets an instance of a specific blueprint."""
37
- logger.debug(f"Getting instance for blueprint: {blueprint_id} with params: {params}")
38
- cache_key = (blueprint_id, tuple(sorted(params.items())) if isinstance(params, dict) else params)
394
+ if messages_to_add_to_db:
395
+ ChatMessage.objects.bulk_create(messages_to_add_to_db)
396
+ logger.debug(f"Stored {len(messages_to_add_to_db)} new messages in DB for conversation {conversation_id}")
397
+ else:
398
+ logger.debug(f"No new response messages to store in DB for conversation {conversation_id}")
399
+
400
+ except Exception as e:
401
+ logger.error(f"Error storing conversation history to DB for {conversation_id}: {e}", exc_info=True)
402
+ # Continue to Redis even if DB fails? Or return False? Let's continue for now.
39
403
 
40
- if params is None and blueprint_id in _blueprint_instance_cache:
41
- logger.debug(f"Returning cached instance for {blueprint_id}")
42
- return _blueprint_instance_cache[blueprint_id]
404
+ # --- Store full history in Redis List ---
405
+ if REDIS_AVAILABLE and redis_client:
406
+ try:
407
+ # Use a Redis list (LPUSH/LTRIM or RPUSH/LTRIM for potentially capped history)
408
+ # For simplicity, let's replace the entire history for now.
409
+ # Delete existing list and push all new items. Use pipeline for atomicity.
410
+ pipe = redis_client.pipeline()
411
+ pipe.delete(conversation_id)
412
+ # Push each message as a separate JSON string onto the list
413
+ for msg_dict in history_to_store:
414
+ pipe.rpush(conversation_id, json.dumps(msg_dict))
415
+ # Optionally cap the list size
416
+ # max_redis_history = 100 # Example cap
417
+ # pipe.ltrim(conversation_id, -max_redis_history, -1)
418
+ pipe.execute()
419
+ logger.debug(f"Stored {len(history_to_store)} messages in Redis list for conversation {conversation_id}")
420
+ except redis.exceptions.RedisError as e:
421
+ logger.error(f"Redis error storing history list for {conversation_id}: {e}", exc_info=True)
422
+ return False # Indicate failure if Redis write fails
423
+ except Exception as e:
424
+ logger.error(f"Unexpected error storing to Redis for {conversation_id}: {e}", exc_info=True)
425
+ return False
43
426
 
44
- available_blueprint_classes = await get_available_blueprints()
427
+ return True
45
428
 
46
- if not isinstance(available_blueprint_classes, dict) or blueprint_id not in available_blueprint_classes:
47
- logger.error(f"Blueprint ID '{blueprint_id}' not found in available blueprint classes.")
48
- return None
49
429
 
50
- blueprint_class = available_blueprint_classes[blueprint_id]
430
+ async def run_conversation(blueprint_instance: Any, messages_extended: List[dict], context_vars: dict) -> Tuple[Any, dict]:
431
+ """Run a conversation turn with a blueprint instance asynchronously."""
432
+ if not isinstance(blueprint_instance, BlueprintBase):
433
+ # Handle LLM passthrough case if needed, or raise error
434
+ # For now, assume it must be a BlueprintBase instance
435
+ logger.error("run_conversation called with non-blueprint instance.")
436
+ raise TypeError("run_conversation expects a BlueprintBase instance.")
51
437
 
52
438
  try:
53
- # *** Instantiate the class WITHOUT the params argument ***
54
- # If blueprints need params, they should handle it internally
55
- # or the base class __init__ needs to accept **kwargs.
56
- instance = blueprint_class()
57
- logger.info(f"Successfully instantiated blueprint: {blueprint_id}")
58
- # Optionally pass params later if needed, e.g., instance.set_params(params) if such a method exists
59
- if hasattr(instance, 'set_params') and callable(getattr(instance, 'set_params')):
60
- instance.set_params(params) # Example of setting params after init
61
-
62
- if params is None:
63
- _blueprint_instance_cache[blueprint_id] = instance
64
- return instance
439
+ # Directly await the async method
440
+ result_dict = await blueprint_instance.run_with_context_async(messages_extended, context_vars)
441
+
442
+ response_obj = result_dict.get("response")
443
+ updated_context = result_dict.get("context_variables", context_vars) # Fallback to original context
444
+
445
+ if response_obj is None:
446
+ logger.error("Blueprint run returned None in response object.")
447
+ # Create a default error response
448
+ response_obj = {"messages": [{"role": "assistant", "content": "Error: Blueprint failed to return a response."}]}
449
+
450
+ return response_obj, updated_context
65
451
  except Exception as e:
66
- # Catch potential TypeError during instantiation too
67
- logger.error(f"Failed to instantiate blueprint class '{blueprint_id}': {e}", exc_info=True)
68
- return None
69
-
70
- # --- Model Access Validation ---
71
- def validate_model_access(user, model_name):
72
- """Synchronous permission check."""
73
- logger.debug(f"Validating access for user '{user}' to model '{model_name}'...")
74
- try:
75
- available = async_to_sync(get_available_blueprints)()
76
- is_available = model_name in available
77
- logger.debug(f"Model '{model_name}' availability: {is_available}")
78
- return is_available
79
- except Exception as e:
80
- logger.error(f"Error checking model availability during validation: {e}", exc_info=True)
81
- return False
452
+ logger.error(f"Exception during blueprint run: {e}", exc_info=True)
453
+ # Return an error structure
454
+ error_response = {"messages": [{"role": "assistant", "content": f"Error processing request: {e}"}]}
455
+ return error_response, context_vars # Return original context on error
swarm/views/web_views.py CHANGED
@@ -14,11 +14,11 @@ from django.contrib.auth.models import User
14
14
 
15
15
  from swarm.utils.logger_setup import setup_logger
16
16
  # Import the function to discover blueprints dynamically
17
- from swarm.core.blueprint_discovery import discover_blueprints
17
+ from swarm.extensions.blueprint.blueprint_discovery import discover_blueprints
18
18
  # Import the setting for the blueprints directory
19
19
  from swarm.settings import BLUEPRINTS_DIR
20
20
  # Import config loader if needed, or assume config is loaded elsewhere
21
- from swarm.core import config_loader, server_config
21
+ from swarm.extensions.config.config_loader import load_server_config
22
22
 
23
23
  logger = setup_logger(__name__)
24
24