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
@@ -0,0 +1,166 @@
1
+ """
2
+ Chat Completion Module
3
+
4
+ This module handles chat completion logic for the Swarm framework, including message preparation,
5
+ tool call repair, and interaction with the OpenAI API. Located in llm/ for LLM-specific functionality.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import logging
11
+ from typing import List, Optional, Dict, Any
12
+ from collections import defaultdict
13
+
14
+ import asyncio
15
+ from openai import AsyncOpenAI, OpenAIError
16
+ from ..types import ChatCompletionMessage, Agent
17
+ from ..utils.redact import redact_sensitive_data
18
+ from ..utils.general_utils import serialize_datetime
19
+ from ..utils.message_utils import filter_duplicate_system_messages, update_null_content
20
+ from ..utils.context_utils import get_token_count, truncate_message_history
21
+ from ..utils.message_sequence import repair_message_payload
22
+
23
+ # Configure module-level logging
24
+ logger = logging.getLogger(__name__)
25
+ logger.setLevel(logging.DEBUG)
26
+ if not logger.handlers:
27
+ stream_handler = logging.StreamHandler()
28
+ formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s - %(message)s")
29
+ stream_handler.setFormatter(formatter)
30
+ logger.addHandler(stream_handler)
31
+
32
+
33
+ async def get_chat_completion(
34
+ client: AsyncOpenAI,
35
+ agent: Agent,
36
+ history: List[Dict[str, Any]],
37
+ context_variables: dict,
38
+ current_llm_config: Dict[str, Any],
39
+ max_context_tokens: int,
40
+ max_context_messages: int,
41
+ model_override: Optional[str] = None,
42
+ stream: bool = False,
43
+ debug: bool = False
44
+ ) -> ChatCompletionMessage:
45
+ """
46
+ Retrieve a chat completion from the LLM for the given agent and history.
47
+
48
+ Args:
49
+ client: AsyncOpenAI client instance.
50
+ agent: The agent processing the completion.
51
+ history: List of previous messages in the conversation.
52
+ context_variables: Variables to include in the agent's context.
53
+ current_llm_config: Current LLM configuration dictionary.
54
+ max_context_tokens: Maximum token limit for context.
55
+ max_context_messages: Maximum message limit for context.
56
+ model_override: Optional model to use instead of default.
57
+ stream: If True, stream the response; otherwise, return complete.
58
+ debug: If True, log detailed debugging information.
59
+
60
+ Returns:
61
+ ChatCompletionMessage: The LLM's response message.
62
+ """
63
+ if not agent:
64
+ logger.error("Cannot generate chat completion: Agent is None")
65
+ raise ValueError("Agent is required")
66
+
67
+ logger.debug(f"Generating chat completion for agent '{agent.name}'")
68
+ active_model = model_override or current_llm_config.get("model", "default")
69
+ client_kwargs = {
70
+ "api_key": current_llm_config.get("api_key"),
71
+ "base_url": current_llm_config.get("base_url")
72
+ }
73
+ client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
74
+ redacted_kwargs = redact_sensitive_data(client_kwargs, sensitive_keys=["api_key"])
75
+ logger.debug(f"Using client with model='{active_model}', base_url='{client_kwargs.get('base_url', 'default')}', api_key={redacted_kwargs['api_key']}")
76
+
77
+ context_variables = defaultdict(str, context_variables)
78
+ instructions = agent.instructions(context_variables) if callable(agent.instructions) else agent.instructions
79
+ if not isinstance(instructions, str):
80
+ logger.warning(f"Invalid instructions type for '{agent.name}': {type(instructions)}. Converting to string.")
81
+ instructions = str(instructions)
82
+ messages = repair_message_payload([{"role": "system", "content": instructions}], debug=debug)
83
+
84
+ if not isinstance(history, list):
85
+ logger.error(f"Invalid history type for '{agent.name}': {type(history)}. Expected list.")
86
+ history = []
87
+ seen_ids = set()
88
+ for msg in history:
89
+ msg_id = msg.get("id", hash(json.dumps(msg, sort_keys=True, default=serialize_datetime)))
90
+ if msg_id not in seen_ids:
91
+ seen_ids.add(msg_id)
92
+ if "tool_calls" in msg and msg["tool_calls"] is not None and not isinstance(msg["tool_calls"], list):
93
+ logger.warning(f"Invalid tool_calls in history for '{msg.get('sender', 'unknown')}': {msg['tool_calls']}. Setting to None.")
94
+ msg["tool_calls"] = None
95
+ messages.append(msg)
96
+ messages = filter_duplicate_system_messages(messages)
97
+ messages = truncate_message_history(messages, active_model, max_context_tokens, max_context_messages)
98
+ messages = repair_message_payload(messages, debug=debug) # Ensure tool calls are paired post-truncation
99
+
100
+ logger.debug(f"Prepared {len(messages)} messages for '{agent.name}'")
101
+ if debug:
102
+ logger.debug(f"Messages: {json.dumps(messages, indent=2, default=str)}")
103
+
104
+ create_params = {
105
+ "model": active_model,
106
+ "messages": messages,
107
+ "stream": stream,
108
+ "temperature": current_llm_config.get("temperature", 0.7),
109
+ }
110
+ if getattr(agent, "response_format", None):
111
+ create_params["response_format"] = agent.response_format
112
+ create_params = {k: v for k, v in create_params.items() if v is not None}
113
+ logger.debug(f"Chat completion params: model='{active_model}', messages_count={len(messages)}, stream={stream}")
114
+
115
+ try:
116
+ logger.debug(f"Calling OpenAI API for '{agent.name}' with model='{active_model}'")
117
+ prev_openai_api_key = os.environ.pop("OPENAI_API_KEY", None)
118
+ try:
119
+ completion = await client.chat.completions.create(**create_params)
120
+ if stream:
121
+ return completion # Return stream object directly
122
+ if completion.choices and len(completion.choices) > 0 and completion.choices[0].message:
123
+ log_msg = completion.choices[0].message.content[:50] if completion.choices[0].message.content else "No content"
124
+ logger.debug(f"OpenAI completion received for '{agent.name}': {log_msg}...")
125
+ return completion.choices[0].message
126
+ else:
127
+ logger.warning(f"No valid message in completion for '{agent.name}'")
128
+ return ChatCompletionMessage(content="No response generated", role="assistant")
129
+ finally:
130
+ if prev_openai_api_key is not None:
131
+ os.environ["OPENAI_API_KEY"] = prev_openai_api_key
132
+ except OpenAIError as e:
133
+ logger.error(f"Chat completion failed for '{agent.name}': {e}")
134
+ raise
135
+
136
+
137
+ async def get_chat_completion_message(
138
+ client: AsyncOpenAI,
139
+ agent: Agent,
140
+ history: List[Dict[str, Any]],
141
+ context_variables: dict,
142
+ current_llm_config: Dict[str, Any],
143
+ max_context_tokens: int,
144
+ max_context_messages: int,
145
+ model_override: Optional[str] = None,
146
+ stream: bool = False,
147
+ debug: bool = False
148
+ ) -> ChatCompletionMessage:
149
+ """
150
+ Wrapper to retrieve and validate a chat completion message.
151
+
152
+ Args:
153
+ Same as get_chat_completion.
154
+
155
+ Returns:
156
+ ChatCompletionMessage: Validated LLM response message.
157
+ """
158
+ logger.debug(f"Fetching chat completion message for '{agent.name}'")
159
+ completion = await get_chat_completion(
160
+ client, agent, history, context_variables, current_llm_config,
161
+ max_context_tokens, max_context_messages, model_override, stream, debug
162
+ )
163
+ if isinstance(completion, ChatCompletionMessage):
164
+ return completion
165
+ logger.warning(f"Unexpected completion type: {type(completion)}. Converting to ChatCompletionMessage.")
166
+ return ChatCompletionMessage(content=str(completion), role="assistant")
swarm/serializers.py CHANGED
@@ -1,103 +1,12 @@
1
1
  from rest_framework import serializers
2
- from swarm.models import ChatMessage
3
- import logging
4
-
5
- logger = logging.getLogger(__name__)
6
- print_logger = logging.getLogger('print_debug')
7
-
8
- class MessageSerializer(serializers.Serializer):
9
- role = serializers.ChoiceField(choices=["system", "user", "assistant", "tool"])
10
- # Content is CharField, allows null/blank by default
11
- content = serializers.CharField(allow_null=True, required=False, allow_blank=True)
12
- name = serializers.CharField(required=False, allow_blank=True)
13
-
14
- # Removed validate_content
15
-
16
- def validate(self, data):
17
- """Validate message structure based on role."""
18
- print_logger.debug(f"MessageSerializer.validate received data: {data}")
19
- role = data.get('role')
20
- content = data.get('content', None)
21
- name = data.get('name')
22
-
23
- # Role validation
24
- if 'role' not in data:
25
- raise serializers.ValidationError({"role": ["This field is required."]})
26
-
27
- # Content requiredness validation (based on role)
28
- content_required = role in ['system', 'user', 'assistant', 'tool']
29
- content_present = 'content' in data
30
-
31
- if content_required:
32
- if not content_present:
33
- raise serializers.ValidationError({"content": ["This field is required."]})
34
- # Null/Blank checks are handled by field definition (allow_null/allow_blank)
35
- # Type check will happen in ChatCompletionRequestSerializer.validate_messages
36
-
37
- # Name validation for tool role
38
- if role == 'tool' and not name:
39
- raise serializers.ValidationError({"name": ["This field is required for role 'tool'."]})
40
-
41
- print_logger.debug(f"MessageSerializer.validate PASSED for data: {data}")
42
- return data
43
-
44
- class ChatCompletionRequestSerializer(serializers.Serializer):
45
- model = serializers.CharField(max_length=255)
46
- messages = MessageSerializer(many=True, min_length=1)
47
- stream = serializers.BooleanField(default=False)
48
- params = serializers.JSONField(required=False, allow_null=True)
49
-
50
- def validate(self, data):
51
- """Perform object-level validation."""
52
- model_value = self.initial_data.get('model')
53
- logger.debug(f"Top-level validate checking model type. Got: {type(model_value)}, value: {model_value}")
54
- if model_value is not None and not isinstance(model_value, str):
55
- raise serializers.ValidationError({"model": ["Field 'model' must be a string."]})
56
- # Messages validation (including content type) happens in validate_messages
57
- return data
58
-
59
- def validate_messages(self, value):
60
- """
61
- Validate the messages list itself and perform raw type checks.
62
- 'value' here is the list *after* MessageSerializer has run on each item.
63
- We need to inspect `self.initial_data` for the raw types.
64
- """
65
- if not value:
66
- raise serializers.ValidationError("Messages list cannot be empty.")
67
-
68
- # Access raw message data from initial_data for type checking
69
- raw_messages = self.initial_data.get('messages', [])
70
- if not isinstance(raw_messages, list):
71
- # This case is handled by ListField implicitly, but good to be explicit
72
- raise serializers.ValidationError("Expected a list of message items.")
73
-
74
- errors = []
75
- for i, raw_msg in enumerate(raw_messages):
76
- msg_errors = {}
77
- if not isinstance(raw_msg, dict):
78
- # If the item itself isn't a dict, add error and skip further checks for it
79
- errors.append({f"item_{i}": "Each message must be a dictionary."})
80
- continue
81
-
82
- # *** Check raw content type here ***
83
- content = raw_msg.get('content', None)
84
- if 'content' in raw_msg and content is not None and not isinstance(content, str):
85
- msg_errors['content'] = ["Content must be a string or null."] # Match test assertion
86
-
87
- # Add other raw checks if needed (e.g., role type)
88
-
89
- if msg_errors:
90
- errors.append(msg_errors) # Append errors for this specific message index
91
-
92
- if errors:
93
- # Raise a single validation error containing all message-specific errors
94
- raise serializers.ValidationError(errors)
95
-
96
- # Return the processed 'value' which passed MessageSerializer validation
97
- return value
2
+ from swarm.models import ChatMessage, ChatConversation
98
3
 
99
4
  class ChatMessageSerializer(serializers.ModelSerializer):
100
5
  class Meta:
101
6
  model = ChatMessage
102
7
  fields = '__all__'
103
8
 
9
+ class ChatConversationSerializer(serializers.ModelSerializer):
10
+ class Meta:
11
+ model = ChatConversation
12
+ fields = '__all__'
swarm/settings.py CHANGED
@@ -3,44 +3,27 @@ Django settings for swarm project.
3
3
  """
4
4
 
5
5
  import os
6
+ import sys
6
7
  from pathlib import Path
7
- from dotenv import load_dotenv
8
8
  import logging
9
9
 
10
- BASE_DIR = Path(__file__).resolve().parent.parent # Points to src/
10
+ # Build paths inside the project like this: BASE_DIR / 'subdir'.
11
+ BASE_DIR = Path(__file__).resolve().parent.parent
12
+ PROJECT_ROOT = BASE_DIR.parent
13
+ if str(PROJECT_ROOT) not in sys.path:
14
+ sys.path.insert(0, str(PROJECT_ROOT))
11
15
 
12
- # --- Load .env file ---
13
- dotenv_path = BASE_DIR.parent / '.env'
14
- load_dotenv(dotenv_path=dotenv_path)
15
- # print(f"[Settings] Attempted to load .env from: {dotenv_path}")
16
- # ---
16
+ BLUEPRINTS_DIR = PROJECT_ROOT / 'blueprints'
17
17
 
18
- SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'django-insecure-fallback-key-for-dev')
19
- DEBUG = os.getenv('DJANGO_DEBUG', 'True').lower() in ('true', '1', 't')
20
- ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')
18
+ # --- Determine if running under pytest ---
19
+ TESTING = 'pytest' in sys.modules
21
20
 
22
- # --- Custom Swarm Settings ---
23
- # Load the token from environment
24
- _raw_api_token = os.getenv('API_AUTH_TOKEN')
25
-
26
- # *** Only enable API auth if the token is actually set ***
27
- ENABLE_API_AUTH = bool(_raw_api_token)
28
- SWARM_API_KEY = _raw_api_token # Assign the loaded token (or None)
29
-
30
- if ENABLE_API_AUTH:
31
- # Add assertion to satisfy type checkers within this block
32
- assert SWARM_API_KEY is not None, "SWARM_API_KEY cannot be None when ENABLE_API_AUTH is True"
33
- # print(f"[Settings] SWARM_API_KEY loaded: {SWARM_API_KEY[:4]}...{SWARM_API_KEY[-4:]}")
34
- # print("[Settings] ENABLE_API_AUTH is True.")
35
- else:
36
- # print("[Settings] API_AUTH_TOKEN env var not set. SWARM_API_KEY is None.")
37
- # print("[Settings] ENABLE_API_AUTH is False.")
38
- pass
39
-
40
- SWARM_CONFIG_PATH = os.getenv('SWARM_CONFIG_PATH', str(BASE_DIR.parent / 'swarm_config.json'))
41
- BLUEPRINT_DIRECTORY = os.getenv('BLUEPRINT_DIRECTORY', str(BASE_DIR / 'swarm' / 'blueprints'))
42
- # --- End Custom Swarm Settings ---
21
+ # Quick-start development settings - unsuitable for production
22
+ SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'django-insecure-YOUR_FALLBACK_KEY_HERE_CHANGE_ME')
23
+ DEBUG = os.getenv('DEBUG', 'True') == 'True'
24
+ ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', '*').split(',')
43
25
 
26
+ # --- Application definition ---
44
27
  INSTALLED_APPS = [
45
28
  'django.contrib.admin',
46
29
  'django.contrib.auth',
@@ -48,20 +31,73 @@ INSTALLED_APPS = [
48
31
  'django.contrib.sessions',
49
32
  'django.contrib.messages',
50
33
  'django.contrib.staticfiles',
34
+ # Third-party apps
51
35
  'rest_framework',
52
36
  'rest_framework.authtoken',
53
37
  'drf_spectacular',
54
- 'swarm',
38
+ # Local apps
39
+ 'swarm.apps.SwarmConfig',
55
40
  ]
56
41
 
42
+ # --- Conditionally add blueprint apps for TESTING ---
43
+ # This ensures the app is known *before* django.setup() is called by pytest-django
44
+ if TESTING:
45
+ # Add specific apps needed for testing
46
+ # We know 'university' is needed based on SWARM_BLUEPRINTS in conftest
47
+ _test_apps_to_add = ['blueprints.university'] # Hardcoding for University tests specifically
48
+ for app in _test_apps_to_add:
49
+ if app not in INSTALLED_APPS:
50
+ # Use insert for potentially better ordering if it matters, otherwise append is fine
51
+ INSTALLED_APPS.insert(0, app) # Or INSTALLED_APPS.append(app)
52
+ logging.info(f"Settings [TESTING]: Added '{app}' to INSTALLED_APPS.")
53
+ # Ensure SWARM_BLUEPRINTS is set if your conftest or other logic relies on it
54
+ # Note: Setting it here might be redundant if conftest sets it too.
55
+ if 'SWARM_BLUEPRINTS' not in os.environ:
56
+ os.environ['SWARM_BLUEPRINTS'] = 'university'
57
+ logging.info(f"Settings [TESTING]: Set SWARM_BLUEPRINTS='university'")
58
+
59
+ else:
60
+ # --- Dynamic App Loading for Production/Development ---
61
+ _INITIAL_BLUEPRINT_APPS = []
62
+ _swarm_blueprints_env = os.getenv('SWARM_BLUEPRINTS')
63
+ _log_source = "Not Set"
64
+ if _swarm_blueprints_env:
65
+ _blueprint_names = [name.strip() for name in _swarm_blueprints_env.split(',') if name.strip()]
66
+ _INITIAL_BLUEPRINT_APPS = [f'blueprints.{name}' for name in _blueprint_names if name.replace('_', '').isidentifier()]
67
+ _log_source = "SWARM_BLUEPRINTS env var"
68
+ logging.info(f"Settings: Found blueprints from env var: {_INITIAL_BLUEPRINT_APPS}")
69
+ else:
70
+ _log_source = "directory scan"
71
+ try:
72
+ if BLUEPRINTS_DIR.is_dir():
73
+ for item in BLUEPRINTS_DIR.iterdir():
74
+ if item.is_dir() and (item / '__init__.py').exists():
75
+ if item.name.replace('_', '').isidentifier():
76
+ _INITIAL_BLUEPRINT_APPS.append(f'blueprints.{item.name}')
77
+ logging.info(f"Settings: Found blueprints from directory scan: {_INITIAL_BLUEPRINT_APPS}")
78
+ except Exception as e:
79
+ logging.error(f"Settings: Error discovering blueprint apps during initial load: {e}")
80
+
81
+ # Add dynamically discovered apps for non-testing scenarios
82
+ for app in _INITIAL_BLUEPRINT_APPS:
83
+ if app not in INSTALLED_APPS:
84
+ INSTALLED_APPS.append(app)
85
+ logging.info(f"Settings [{_log_source}]: Added '{app}' to INSTALLED_APPS.")
86
+ # --- End App Loading Logic ---
87
+
88
+ # Ensure INSTALLED_APPS is a list for compatibility
89
+ if isinstance(INSTALLED_APPS, tuple):
90
+ INSTALLED_APPS = list(INSTALLED_APPS)
91
+
92
+ logging.info(f"Settings: Final INSTALLED_APPS = {INSTALLED_APPS}")
93
+
94
+
57
95
  MIDDLEWARE = [
58
96
  'django.middleware.security.SecurityMiddleware',
59
97
  'django.contrib.sessions.middleware.SessionMiddleware',
60
98
  'django.middleware.common.CommonMiddleware',
61
99
  'django.middleware.csrf.CsrfViewMiddleware',
62
100
  'django.contrib.auth.middleware.AuthenticationMiddleware',
63
- # Add custom middleware to handle async user loading after standard auth
64
- 'swarm.middleware.AsyncAuthMiddleware',
65
101
  'django.contrib.messages.middleware.MessageMiddleware',
66
102
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
67
103
  ]
@@ -71,7 +107,7 @@ ROOT_URLCONF = 'swarm.urls'
71
107
  TEMPLATES = [
72
108
  {
73
109
  'BACKEND': 'django.template.backends.django.DjangoTemplates',
74
- 'DIRS': [BASE_DIR.parent / 'templates'],
110
+ 'DIRS': [BASE_DIR / 'templates'],
75
111
  'APP_DIRS': True,
76
112
  'OPTIONS': {
77
113
  'context_processors': [
@@ -85,93 +121,105 @@ TEMPLATES = [
85
121
  ]
86
122
 
87
123
  WSGI_APPLICATION = 'swarm.wsgi.application'
88
- ASGI_APPLICATION = 'swarm.asgi.application'
89
124
 
125
+ # Database
126
+ SQLITE_DB_PATH = os.getenv('SQLITE_DB_PATH', BASE_DIR / 'db.sqlite3')
90
127
  DATABASES = {
91
128
  'default': {
92
129
  'ENGINE': 'django.db.backends.sqlite3',
93
- 'NAME': os.environ.get('DJANGO_DB_NAME', '/tmp/db.sqlite3'),
94
- 'TEST': {
95
- 'NAME': os.environ.get('DJANGO_TEST_DB_NAME', '/tmp/test_db.sqlite3'),
96
- 'OPTIONS': {
97
- 'timeout': 20,
98
- 'init_command': "PRAGMA journal_mode=WAL;",
99
- },
100
- },
130
+ 'NAME': SQLITE_DB_PATH,
101
131
  }
102
132
  }
133
+ DJANGO_DATABASE = DATABASES['default']
103
134
 
135
+
136
+ # Password validation
104
137
  AUTH_PASSWORD_VALIDATORS = [
105
- {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},
106
- {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',},
107
- {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',},
108
- {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',},
138
+ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
139
+ {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
140
+ {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
141
+ {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
109
142
  ]
110
143
 
144
+
145
+ # Internationalization
111
146
  LANGUAGE_CODE = 'en-us'
112
147
  TIME_ZONE = 'UTC'
113
148
  USE_I18N = True
114
149
  USE_TZ = True
115
150
 
116
- STATIC_URL = 'static/'
117
- STATIC_ROOT = BASE_DIR.parent / 'staticfiles'
118
- STATICFILES_DIRS = [ BASE_DIR / "swarm" / "static", ]
119
151
 
152
+ # Static files
153
+ STATIC_URL = '/static/'
154
+ STATIC_ROOT = BASE_DIR / 'staticfiles'
155
+ STATICFILES_DIRS = [
156
+ BASE_DIR / 'static',
157
+ BASE_DIR / 'assets',
158
+ ]
159
+
160
+ # Default primary key field type
120
161
  DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
121
162
 
163
+ # REST Framework settings
122
164
  REST_FRAMEWORK = {
123
- 'DEFAULT_AUTHENTICATION_CLASSES': [
124
- 'swarm.auth.StaticTokenAuthentication',
125
- 'swarm.auth.CustomSessionAuthentication',
126
- ],
127
- # *** IMPORTANT: Add DEFAULT_PERMISSION_CLASSES ***
128
- # If ENABLE_API_AUTH is False, we might want to allow any access for testing.
129
- # If ENABLE_API_AUTH is True, we require HasValidTokenOrSession.
130
- # We need to set this dynamically based on ENABLE_API_AUTH.
131
- # A simple way is to set it here, but a cleaner way might involve middleware
132
- # or overriding get_permissions in views. For now, let's adjust this:
133
- 'DEFAULT_PERMISSION_CLASSES': [
134
- # If auth is enabled, require our custom permission
135
- 'swarm.permissions.HasValidTokenOrSession' if ENABLE_API_AUTH else
136
- # Otherwise, allow anyone (useful for dev when token isn't set)
137
- 'rest_framework.permissions.AllowAny'
138
- ],
139
165
  'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
166
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
167
+ 'swarm.auth.EnvOrTokenAuthentication',
168
+ 'rest_framework.authentication.TokenAuthentication',
169
+ 'rest_framework.authentication.SessionAuthentication',
170
+ ),
171
+ 'DEFAULT_PERMISSION_CLASSES': (
172
+ 'rest_framework.permissions.IsAuthenticated',
173
+ )
140
174
  }
141
175
 
142
176
  SPECTACULAR_SETTINGS = {
143
177
  'TITLE': 'Open Swarm API',
144
- 'DESCRIPTION': 'API for managing autonomous agent swarms',
145
- 'VERSION': '0.2.0',
178
+ 'DESCRIPTION': 'API for the Open Swarm multi-agent collaboration framework.',
179
+ 'VERSION': '1.0.0',
146
180
  'SERVE_INCLUDE_SCHEMA': False,
181
+ 'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'],
147
182
  }
148
183
 
184
+ # Logging configuration
149
185
  LOGGING = {
150
186
  'version': 1,
151
187
  'disable_existing_loggers': False,
152
188
  'formatters': {
153
- 'verbose': { 'format': '[{levelname}] {asctime} - {name}:{lineno} - {message}', 'style': '{', },
154
- 'simple': { 'format': '[{levelname}] {message}', 'style': '{', },
189
+ 'standard': {
190
+ 'format': '[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s'
191
+ },
155
192
  },
156
193
  'handlers': {
157
- 'console': { 'class': 'logging.StreamHandler', 'formatter': 'verbose', },
194
+ 'console': {
195
+ 'level': 'DEBUG' if DEBUG else 'INFO',
196
+ 'class': 'logging.StreamHandler',
197
+ 'formatter': 'standard',
198
+ },
158
199
  },
159
200
  'loggers': {
160
- 'django': { 'handlers': ['console'], 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), 'propagate': False, },
161
- 'swarm': { 'handlers': ['console'], 'level': os.getenv('SWARM_LOG_LEVEL', 'DEBUG'), 'propagate': False, },
162
- 'swarm.auth': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, },
163
- 'swarm.views': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, },
164
- 'swarm.extensions': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, },
165
- 'blueprint_django_chat': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, },
166
- 'print_debug': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, },
201
+ 'django': { 'handlers': ['console'], 'level': 'INFO', 'propagate': False, },
202
+ 'django.request': { 'handlers': ['console'], 'level': 'WARNING', 'propagate': False, },
203
+ 'swarm': { 'handlers': ['console'], 'level': 'DEBUG' if DEBUG else 'INFO', 'propagate': False, },
204
+ 'swarm.extensions': { 'handlers': ['console'], 'level': 'DEBUG' if DEBUG else 'INFO', 'propagate': False, },
205
+ 'blueprints': { 'handlers': ['console'], 'level': 'DEBUG' if DEBUG else 'INFO', 'propagate': False, },
167
206
  },
168
- 'root': { 'handlers': ['console'], 'level': 'WARNING', },
169
207
  }
170
208
 
209
+ # Authentication backends
210
+ AUTHENTICATION_BACKENDS = [
211
+ 'django.contrib.auth.backends.ModelBackend',
212
+ ]
213
+
214
+ # Login URL
215
+ LOGIN_URL = '/accounts/login/'
216
+ LOGIN_REDIRECT_URL = '/chatbot/'
217
+
218
+ # Redis settings
171
219
  REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
172
- REDIS_PORT = int(os.getenv('REDIS_PORT', '6379'))
220
+ REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
173
221
 
174
- LOGIN_URL = '/login/'
175
- LOGIN_REDIRECT_URL = '/'
176
- LOGOUT_REDIRECT_URL = '/'
177
- CSRF_TRUSTED_ORIGINS = os.getenv('DJANGO_CSRF_TRUSTED_ORIGINS', 'http://localhost:8000,http://127.0.0.1:8000').split(',')
222
+ # Adjust DB for testing if TESTING flag is set
223
+ if TESTING:
224
+ print("Pytest detected: Adjusting settings for testing.")
225
+ DATABASES['default']['NAME'] = ':memory:'
swarm/types.py ADDED
@@ -0,0 +1,91 @@
1
+ from openai.types.chat import ChatCompletionMessage
2
+ from openai.types.chat.chat_completion_message_tool_call import (
3
+ ChatCompletionMessageToolCall,
4
+ Function,
5
+ )
6
+ from typing import List, Callable, Union, Optional, Dict, Any
7
+
8
+ from pydantic import BaseModel, ConfigDict
9
+
10
+ # AgentFunction = Callable[[], Union[str, "Agent", dict]]
11
+ AgentFunction = Callable[..., Union[str, "Agent", dict]]
12
+
13
+ class Agent(BaseModel):
14
+ # model_config = ConfigDict(arbitrary_types_allowed=True) # Allow non-Pydantic types (for nemo guardrails instance)
15
+
16
+ name: str = "Agent"
17
+ model: str = "default"
18
+ instructions: Union[str, Callable[[], str]] = "You are a helpful agent."
19
+ functions: List[AgentFunction] = []
20
+ resources: List[Dict[str, Any]] = [] # New attribute for static and MCP-discovered resources
21
+ tool_choice: str = None
22
+ # parallel_tool_calls: bool = True # Commented out as in your version
23
+ parallel_tool_calls: bool = False
24
+ mcp_servers: Optional[List[str]] = None # List of MCP server names
25
+ env_vars: Optional[Dict[str, str]] = None # Environment variables required
26
+ response_format: Optional[Dict[str, Any]] = None # Structured Output
27
+
28
+ class Response(BaseModel):
29
+ id: Optional[str] = None # id needed for REST
30
+ messages: List = [] # Adjusted to allow any list (flexible for messages)
31
+ agent: Optional[Agent] = None
32
+ context_variables: dict = {}
33
+
34
+ def __init__(self, **kwargs):
35
+ super().__init__(**kwargs)
36
+ # Automatically generate an ID if not provided
37
+ if not self.id:
38
+ import uuid
39
+ self.id = f"response-{uuid.uuid4()}"
40
+
41
+ class Result(BaseModel):
42
+ """
43
+ Encapsulates the possible return values for an agent function.
44
+
45
+ Attributes:
46
+ value (str): The result value as a string.
47
+ agent (Agent): The agent instance, if applicable.
48
+ context_variables (dict): A dictionary of context variables.
49
+ """
50
+ value: str = ""
51
+ agent: Optional[Agent] = None
52
+ context_variables: dict = {}
53
+
54
+ class Tool:
55
+ def __init__(
56
+ self,
57
+ name: str,
58
+ func: Callable,
59
+ description: str = "",
60
+ input_schema: Optional[Dict[str, Any]] = None,
61
+ dynamic: bool = False,
62
+ ):
63
+ """
64
+ Initialize a Tool object.
65
+
66
+ :param name: Name of the tool.
67
+ :param func: The callable associated with this tool.
68
+ :param description: A brief description of the tool.
69
+ :param input_schema: Schema defining the inputs the tool accepts.
70
+ :param dynamic: Whether this tool is dynamically generated.
71
+ """
72
+ self.name = name
73
+ self.func = func
74
+ self.description = description
75
+ self.input_schema = input_schema or {}
76
+ self.dynamic = dynamic
77
+
78
+ @property
79
+ def __name__(self):
80
+ return self.name
81
+
82
+ @property
83
+ def __code__(self):
84
+ # Return the __code__ of the actual function, or a mock object if missing
85
+ return getattr(self.func, "__code__", None)
86
+
87
+ def __call__(self, *args, **kwargs):
88
+ """
89
+ Make the Tool instance callable.
90
+ """
91
+ return self.func(*args, **kwargs)