open-swarm 0.1.1745274976__py3-none-any.whl → 0.1.1748636259__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 (295) hide show
  1. open_swarm-0.1.1748636259.dist-info/METADATA +188 -0
  2. open_swarm-0.1.1748636259.dist-info/RECORD +82 -0
  3. {open_swarm-0.1.1745274976.dist-info → open_swarm-0.1.1748636259.dist-info}/WHEEL +2 -1
  4. open_swarm-0.1.1748636259.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1748636259.dist-info/top_level.txt +1 -0
  6. swarm/agent/agent.py +49 -0
  7. swarm/auth.py +48 -113
  8. swarm/consumers.py +0 -19
  9. swarm/extensions/blueprint/__init__.py +16 -30
  10. swarm/{core → extensions/blueprint}/agent_utils.py +1 -1
  11. swarm/extensions/blueprint/blueprint_base.py +458 -0
  12. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  13. swarm/extensions/blueprint/output_utils.py +95 -0
  14. swarm/{core → extensions/blueprint}/spinner.py +21 -30
  15. swarm/extensions/cli/cli_args.py +0 -6
  16. swarm/extensions/cli/commands/blueprint_management.py +9 -47
  17. swarm/extensions/cli/commands/config_management.py +6 -5
  18. swarm/extensions/cli/commands/edit_config.py +7 -16
  19. swarm/extensions/cli/commands/list_blueprints.py +1 -1
  20. swarm/extensions/cli/commands/validate_env.py +4 -11
  21. swarm/extensions/cli/commands/validate_envvars.py +6 -6
  22. swarm/extensions/cli/interactive_shell.py +2 -16
  23. swarm/extensions/config/config_loader.py +201 -107
  24. swarm/{core → extensions/config}/config_manager.py +38 -50
  25. swarm/{core → extensions/config}/server_config.py +0 -32
  26. swarm/extensions/launchers/build_launchers.py +14 -0
  27. swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
  28. swarm/extensions/launchers/swarm_api.py +64 -8
  29. swarm/extensions/launchers/swarm_cli.py +300 -8
  30. swarm/llm/chat_completion.py +195 -0
  31. swarm/serializers.py +5 -96
  32. swarm/settings.py +111 -99
  33. swarm/urls.py +74 -57
  34. swarm/utils/context_utils.py +4 -10
  35. swarm/utils/general_utils.py +0 -21
  36. swarm/utils/redact.py +36 -23
  37. swarm/views/api_views.py +39 -48
  38. swarm/views/chat_views.py +70 -237
  39. swarm/views/core_views.py +87 -80
  40. swarm/views/model_views.py +121 -64
  41. swarm/views/utils.py +441 -65
  42. swarm/views/web_views.py +2 -2
  43. open_swarm-0.1.1745274976.dist-info/METADATA +0 -874
  44. open_swarm-0.1.1745274976.dist-info/RECORD +0 -318
  45. open_swarm-0.1.1745274976.dist-info/entry_points.txt +0 -4
  46. swarm/blueprints/README.md +0 -68
  47. swarm/blueprints/blueprint_audit_status.json +0 -27
  48. swarm/blueprints/chatbot/README.md +0 -40
  49. swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
  50. swarm/blueprints/chatbot/metadata.json +0 -23
  51. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
  52. swarm/blueprints/chucks_angels/README.md +0 -11
  53. swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
  54. swarm/blueprints/chucks_angels/test_basic.py +0 -3
  55. swarm/blueprints/codey/CODEY.md +0 -15
  56. swarm/blueprints/codey/README.md +0 -115
  57. swarm/blueprints/codey/blueprint_codey.py +0 -1072
  58. swarm/blueprints/codey/codey_cli.py +0 -373
  59. swarm/blueprints/codey/instructions.md +0 -17
  60. swarm/blueprints/codey/metadata.json +0 -23
  61. swarm/blueprints/common/operation_box_utils.py +0 -83
  62. swarm/blueprints/digitalbutlers/README.md +0 -11
  63. swarm/blueprints/digitalbutlers/__init__.py +0 -1
  64. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
  65. swarm/blueprints/digitalbutlers/test_basic.py +0 -3
  66. swarm/blueprints/divine_code/README.md +0 -3
  67. swarm/blueprints/divine_code/__init__.py +0 -10
  68. swarm/blueprints/divine_code/apps.py +0 -11
  69. swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
  70. swarm/blueprints/django_chat/apps.py +0 -6
  71. swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
  72. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
  73. swarm/blueprints/django_chat/urls.py +0 -8
  74. swarm/blueprints/django_chat/views.py +0 -32
  75. swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
  76. swarm/blueprints/flock/README.md +0 -11
  77. swarm/blueprints/flock/__init__.py +0 -8
  78. swarm/blueprints/flock/blueprint_flock.py +0 -7
  79. swarm/blueprints/flock/test_basic.py +0 -3
  80. swarm/blueprints/geese/README.md +0 -97
  81. swarm/blueprints/geese/blueprint_geese.py +0 -803
  82. swarm/blueprints/geese/geese_cli.py +0 -102
  83. swarm/blueprints/jeeves/README.md +0 -41
  84. swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
  85. swarm/blueprints/jeeves/jeeves_cli.py +0 -55
  86. swarm/blueprints/jeeves/metadata.json +0 -24
  87. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
  88. swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
  89. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
  90. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
  91. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
  92. swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
  93. swarm/blueprints/poets/blueprint_poets.py +0 -546
  94. swarm/blueprints/poets/poets_cli.py +0 -23
  95. swarm/blueprints/rue_code/README.md +0 -8
  96. swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
  97. swarm/blueprints/rue_code/rue_code_cli.py +0 -43
  98. swarm/blueprints/stewie/apps.py +0 -12
  99. swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
  100. swarm/blueprints/stewie/models.py +0 -19
  101. swarm/blueprints/stewie/serializers.py +0 -10
  102. swarm/blueprints/stewie/settings.py +0 -17
  103. swarm/blueprints/stewie/urls.py +0 -11
  104. swarm/blueprints/stewie/views.py +0 -26
  105. swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
  106. swarm/blueprints/whinge_surf/README.md +0 -22
  107. swarm/blueprints/whinge_surf/__init__.py +0 -1
  108. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
  109. swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
  110. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  111. swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
  112. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
  113. swarm/blueprints/zeus/__init__.py +0 -2
  114. swarm/blueprints/zeus/apps.py +0 -4
  115. swarm/blueprints/zeus/blueprint_zeus.py +0 -270
  116. swarm/blueprints/zeus/zeus_cli.py +0 -13
  117. swarm/cli/async_input.py +0 -65
  118. swarm/cli/async_input_demo.py +0 -32
  119. swarm/core/blueprint_base.py +0 -769
  120. swarm/core/blueprint_discovery.py +0 -125
  121. swarm/core/blueprint_runner.py +0 -59
  122. swarm/core/blueprint_ux.py +0 -109
  123. swarm/core/build_launchers.py +0 -15
  124. swarm/core/cli/__init__.py +0 -1
  125. swarm/core/cli/commands/__init__.py +0 -1
  126. swarm/core/cli/commands/blueprint_management.py +0 -7
  127. swarm/core/cli/interactive_shell.py +0 -14
  128. swarm/core/cli/main.py +0 -50
  129. swarm/core/cli/utils/__init__.py +0 -1
  130. swarm/core/cli/utils/discover_commands.py +0 -18
  131. swarm/core/config_loader.py +0 -122
  132. swarm/core/output_utils.py +0 -193
  133. swarm/core/session_logger.py +0 -42
  134. swarm/core/slash_commands.py +0 -89
  135. swarm/core/swarm_api.py +0 -68
  136. swarm/core/swarm_cli.py +0 -216
  137. swarm/core/utils/__init__.py +0 -0
  138. swarm/extensions/blueprint/cli_handler.py +0 -197
  139. swarm/extensions/blueprint/runnable_blueprint.py +0 -42
  140. swarm/extensions/cli/utils/__init__.py +0 -1
  141. swarm/extensions/cli/utils/async_input.py +0 -46
  142. swarm/extensions/cli/utils/prompt_user.py +0 -3
  143. swarm/management/__init__.py +0 -0
  144. swarm/management/commands/__init__.py +0 -0
  145. swarm/management/commands/runserver.py +0 -58
  146. swarm/middleware.py +0 -65
  147. swarm/permissions.py +0 -38
  148. swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
  149. swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
  150. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
  151. swarm/static/contrib/markedjs/marked.min.js +0 -6
  152. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
  153. swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
  154. swarm/static/contrib/tabler-icons/archive.svg +0 -21
  155. swarm/static/contrib/tabler-icons/artboard.svg +0 -27
  156. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
  157. swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
  158. swarm/static/contrib/tabler-icons/carambola.svg +0 -19
  159. swarm/static/contrib/tabler-icons/copy.svg +0 -20
  160. swarm/static/contrib/tabler-icons/download.svg +0 -21
  161. swarm/static/contrib/tabler-icons/edit.svg +0 -21
  162. swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
  163. swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
  164. swarm/static/contrib/tabler-icons/headset.svg +0 -22
  165. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
  166. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
  167. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
  168. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
  169. swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
  170. swarm/static/contrib/tabler-icons/message-star.svg +0 -22
  171. swarm/static/contrib/tabler-icons/message-x.svg +0 -23
  172. swarm/static/contrib/tabler-icons/message.svg +0 -21
  173. swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
  174. swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
  175. swarm/static/contrib/tabler-icons/robot.svg +0 -26
  176. swarm/static/contrib/tabler-icons/search.svg +0 -19
  177. swarm/static/contrib/tabler-icons/settings.svg +0 -20
  178. swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
  179. swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
  180. swarm/static/css/dropdown.css +0 -22
  181. swarm/static/htmx/htmx.min.js +0 -0
  182. swarm/static/js/dropdown.js +0 -23
  183. swarm/static/rest_mode/css/base.css +0 -470
  184. swarm/static/rest_mode/css/chat-history.css +0 -286
  185. swarm/static/rest_mode/css/chat.css +0 -251
  186. swarm/static/rest_mode/css/chatbot.css +0 -74
  187. swarm/static/rest_mode/css/chatgpt.css +0 -62
  188. swarm/static/rest_mode/css/colors/corporate.css +0 -74
  189. swarm/static/rest_mode/css/colors/pastel.css +0 -81
  190. swarm/static/rest_mode/css/colors/tropical.css +0 -82
  191. swarm/static/rest_mode/css/general.css +0 -142
  192. swarm/static/rest_mode/css/layout.css +0 -167
  193. swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
  194. swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
  195. swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
  196. swarm/static/rest_mode/css/messages.css +0 -84
  197. swarm/static/rest_mode/css/messenger.css +0 -135
  198. swarm/static/rest_mode/css/settings.css +0 -91
  199. swarm/static/rest_mode/css/simple.css +0 -44
  200. swarm/static/rest_mode/css/slack.css +0 -58
  201. swarm/static/rest_mode/css/style.css +0 -156
  202. swarm/static/rest_mode/css/theme.css +0 -30
  203. swarm/static/rest_mode/css/toast.css +0 -40
  204. swarm/static/rest_mode/js/auth.js +0 -9
  205. swarm/static/rest_mode/js/blueprint.js +0 -41
  206. swarm/static/rest_mode/js/blueprintUtils.js +0 -12
  207. swarm/static/rest_mode/js/chatLogic.js +0 -79
  208. swarm/static/rest_mode/js/debug.js +0 -63
  209. swarm/static/rest_mode/js/events.js +0 -98
  210. swarm/static/rest_mode/js/main.js +0 -19
  211. swarm/static/rest_mode/js/messages.js +0 -264
  212. swarm/static/rest_mode/js/messengerLogic.js +0 -355
  213. swarm/static/rest_mode/js/modules/apiService.js +0 -84
  214. swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
  215. swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
  216. swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
  217. swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
  218. swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
  219. swarm/static/rest_mode/js/modules/state.js +0 -7
  220. swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
  221. swarm/static/rest_mode/js/modules/validation.js +0 -23
  222. swarm/static/rest_mode/js/rendering.js +0 -119
  223. swarm/static/rest_mode/js/settings.js +0 -130
  224. swarm/static/rest_mode/js/sidebar.js +0 -94
  225. swarm/static/rest_mode/js/simpleLogic.js +0 -37
  226. swarm/static/rest_mode/js/slackLogic.js +0 -66
  227. swarm/static/rest_mode/js/splash.js +0 -76
  228. swarm/static/rest_mode/js/theme.js +0 -111
  229. swarm/static/rest_mode/js/toast.js +0 -36
  230. swarm/static/rest_mode/js/ui.js +0 -265
  231. swarm/static/rest_mode/js/validation.js +0 -57
  232. swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
  233. swarm/static/rest_mode/svg/arrow_down.svg +0 -5
  234. swarm/static/rest_mode/svg/arrow_left.svg +0 -5
  235. swarm/static/rest_mode/svg/arrow_right.svg +0 -5
  236. swarm/static/rest_mode/svg/arrow_up.svg +0 -5
  237. swarm/static/rest_mode/svg/attach.svg +0 -8
  238. swarm/static/rest_mode/svg/avatar.svg +0 -7
  239. swarm/static/rest_mode/svg/canvas.svg +0 -6
  240. swarm/static/rest_mode/svg/chat_history.svg +0 -4
  241. swarm/static/rest_mode/svg/close.svg +0 -5
  242. swarm/static/rest_mode/svg/copy.svg +0 -4
  243. swarm/static/rest_mode/svg/dark_mode.svg +0 -3
  244. swarm/static/rest_mode/svg/edit.svg +0 -5
  245. swarm/static/rest_mode/svg/layout.svg +0 -9
  246. swarm/static/rest_mode/svg/logo.svg +0 -29
  247. swarm/static/rest_mode/svg/logout.svg +0 -5
  248. swarm/static/rest_mode/svg/mobile.svg +0 -5
  249. swarm/static/rest_mode/svg/new_chat.svg +0 -4
  250. swarm/static/rest_mode/svg/not_visible.svg +0 -5
  251. swarm/static/rest_mode/svg/plus.svg +0 -7
  252. swarm/static/rest_mode/svg/run_code.svg +0 -6
  253. swarm/static/rest_mode/svg/save.svg +0 -4
  254. swarm/static/rest_mode/svg/search.svg +0 -6
  255. swarm/static/rest_mode/svg/settings.svg +0 -4
  256. swarm/static/rest_mode/svg/speaker.svg +0 -5
  257. swarm/static/rest_mode/svg/stop.svg +0 -6
  258. swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
  259. swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
  260. swarm/static/rest_mode/svg/toggle_off.svg +0 -6
  261. swarm/static/rest_mode/svg/toggle_on.svg +0 -6
  262. swarm/static/rest_mode/svg/trash.svg +0 -10
  263. swarm/static/rest_mode/svg/undo.svg +0 -3
  264. swarm/static/rest_mode/svg/visible.svg +0 -8
  265. swarm/static/rest_mode/svg/voice.svg +0 -10
  266. swarm/templates/account/login.html +0 -22
  267. swarm/templates/account/signup.html +0 -32
  268. swarm/templates/base.html +0 -30
  269. swarm/templates/chat.html +0 -43
  270. swarm/templates/index.html +0 -35
  271. swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
  272. swarm/templates/rest_mode/components/header.html +0 -45
  273. swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
  274. swarm/templates/rest_mode/components/settings_dialog.html +0 -97
  275. swarm/templates/rest_mode/components/splash_screen.html +0 -7
  276. swarm/templates/rest_mode/components/top_bar.html +0 -28
  277. swarm/templates/rest_mode/message_ui.html +0 -50
  278. swarm/templates/rest_mode/slackbot.html +0 -30
  279. swarm/templates/simple_blueprint_page.html +0 -24
  280. swarm/templates/websocket_partials/final_system_message.html +0 -3
  281. swarm/templates/websocket_partials/system_message.html +0 -4
  282. swarm/templates/websocket_partials/user_message.html +0 -5
  283. swarm/utils/ansi_box.py +0 -34
  284. swarm/utils/disable_tracing.py +0 -38
  285. swarm/utils/log_utils.py +0 -63
  286. swarm/utils/openai_patch.py +0 -33
  287. swarm/ux/ansi_box.py +0 -43
  288. swarm/ux/spinner.py +0 -53
  289. {open_swarm-0.1.1745274976.dist-info → open_swarm-0.1.1748636259.dist-info}/licenses/LICENSE +0 -0
  290. /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
  291. /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
  292. /swarm/{core → extensions/config}/setup_wizard.py +0 -0
  293. /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
  294. /swarm/{core → extensions/config}/utils/logger.py +0 -0
  295. /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -1,12 +1,68 @@
1
- """
2
- Swarm API entry point for installation via PyPI or local dev.
3
- """
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import subprocess
4
4
  import sys
5
- import os
6
- from swarm.core.swarm_api import main
5
+ from os import path, listdir, makedirs
7
6
 
8
- def main_entry():
9
- main()
7
+ def main():
8
+ parser = argparse.ArgumentParser(description="Swarm REST Launcher")
9
+ parser.add_argument("--blueprint", required=True, help="Comma-separated blueprint file paths or names for configuration purposes")
10
+ parser.add_argument("--port", type=int, default=8000, help="Port to run the REST server")
11
+ parser.add_argument("--config", default="~/.swarm/swarm_config.json", help="Configuration file path")
12
+ parser.add_argument("--daemon", action="store_true", help="Run in daemon mode and print process id")
13
+ args = parser.parse_args()
14
+
15
+ # Split blueprints by comma and strip whitespace
16
+ bp_list = [bp.strip() for bp in args.blueprint.split(",") if bp.strip()]
17
+ blueprint_paths = []
18
+ for bp_arg in bp_list:
19
+ resolved = None
20
+ if path.exists(bp_arg):
21
+ if path.isdir(bp_arg):
22
+ resolved = bp_arg
23
+ print(f"Using blueprint directory: {resolved}")
24
+ else:
25
+ resolved = bp_arg
26
+ print(f"Using blueprint file: {resolved}")
27
+ else:
28
+ managed_path = path.expanduser("~/.swarm/blueprints/" + bp_arg)
29
+ if path.isdir(managed_path):
30
+ matches = [f for f in listdir(managed_path) if f.startswith("blueprint_") and f.endswith(".py")]
31
+ if not matches:
32
+ print("Error: No blueprint file found in managed directory:", managed_path)
33
+ sys.exit(1)
34
+ resolved = path.join(managed_path, matches[0])
35
+ print(f"Using managed blueprint: {resolved}")
36
+ else:
37
+ print("Warning: Blueprint not found:", bp_arg, "- skipping.")
38
+ continue
39
+ if resolved:
40
+ blueprint_paths.append(resolved)
41
+
42
+ if not blueprint_paths:
43
+ print("Error: No valid blueprints found.")
44
+ sys.exit(1)
45
+ print("Blueprints to be configured:")
46
+ for bp in blueprint_paths:
47
+ print(" -", bp)
48
+
49
+ config_path = path.expanduser(args.config)
50
+ if not path.exists(config_path):
51
+ makedirs(path.dirname(config_path), exist_ok=True)
52
+ with open(config_path, 'w') as f:
53
+ f.write("{}")
54
+ print("Default config file created at:", config_path)
55
+
56
+ print("Launching Django server on port 0.0.0.0:{}".format(args.port))
57
+ try:
58
+ if args.daemon:
59
+ proc = subprocess.Popen(["python", "manage.py", "runserver", f"0.0.0.0:{args.port}"])
60
+ print("Running in daemon mode. Process ID:", proc.pid)
61
+ else:
62
+ subprocess.run(["python", "manage.py", "runserver", f"0.0.0.0:{args.port}"], check=True)
63
+ except subprocess.CalledProcessError as e:
64
+ print("Error launching Django server:", e)
65
+ sys.exit(1)
10
66
 
11
67
  if __name__ == "__main__":
12
- main_entry()
68
+ main()
@@ -1,12 +1,304 @@
1
- """
2
- Swarm CLI entry point for installation via PyPI or local dev.
3
- """
4
- import sys
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import importlib.util
5
4
  import os
6
- from swarm.extensions.cli.main import main
5
+ import sys
6
+ import subprocess
7
+ import shutil
8
+ import json
9
+ import PyInstaller.__main__
7
10
 
8
- def app():
9
- main()
11
+ def resolve_env_vars(data):
12
+ if isinstance(data, dict):
13
+ return {k: resolve_env_vars(v) for k, v in data.items()}
14
+ elif isinstance(data, list):
15
+ return [resolve_env_vars(item) for item in data]
16
+ elif isinstance(data, str):
17
+ return os.path.expandvars(data)
18
+ else:
19
+ return data
20
+
21
+ MANAGED_DIR = os.path.expanduser("~/.swarm/blueprints")
22
+ BIN_DIR = os.path.expanduser("~/.swarm/bin")
23
+
24
+ def ensure_managed_dir():
25
+ if not os.path.exists(MANAGED_DIR):
26
+ os.makedirs(MANAGED_DIR, exist_ok=True)
27
+ if not os.path.exists(BIN_DIR):
28
+ os.makedirs(BIN_DIR, exist_ok=True)
29
+
30
+ def add_blueprint(source_path, blueprint_name=None):
31
+ source_path = os.path.normpath(source_path)
32
+ if not os.path.exists(source_path):
33
+ print("Error: source file/directory does not exist:", source_path)
34
+ sys.exit(1)
35
+ if os.path.isdir(source_path):
36
+ if not blueprint_name:
37
+ blueprint_name = os.path.basename(os.path.normpath(source_path))
38
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
39
+ if os.path.exists(target_dir):
40
+ shutil.rmtree(target_dir)
41
+ os.makedirs(target_dir, exist_ok=True)
42
+ for root, dirs, files in os.walk(source_path):
43
+ rel_path = os.path.relpath(root, source_path)
44
+ dest_root = os.path.join(target_dir, rel_path) if rel_path != '.' else target_dir
45
+ os.makedirs(dest_root, exist_ok=True)
46
+ for file in files:
47
+ shutil.copy2(os.path.join(root, file), os.path.join(dest_root, file))
48
+ print(f"Blueprint '{blueprint_name}' added successfully to {target_dir}.")
49
+ else:
50
+ blueprint_file = source_path
51
+ if not blueprint_name:
52
+ base = os.path.basename(blueprint_file)
53
+ if base.startswith("blueprint_") and base.endswith(".py"):
54
+ blueprint_name = base[len("blueprint_"):-3]
55
+ else:
56
+ blueprint_name = os.path.splitext(base)[0]
57
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
58
+ os.makedirs(target_dir, exist_ok=True)
59
+ target_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
60
+ shutil.copy2(blueprint_file, target_file)
61
+ print(f"Blueprint '{blueprint_name}' added successfully to {target_dir}.")
62
+
63
+ def list_blueprints():
64
+ ensure_managed_dir()
65
+ entries = os.listdir(MANAGED_DIR)
66
+ blueprints = [d for d in entries if os.path.isdir(os.path.join(MANAGED_DIR, d))]
67
+ if blueprints:
68
+ print("Registered blueprints:")
69
+ for bp in blueprints:
70
+ print(" -", bp)
71
+ else:
72
+ print("No blueprints registered.")
73
+
74
+ def delete_blueprint(blueprint_name):
75
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
76
+ if os.path.exists(target_dir) and os.path.isdir(target_dir):
77
+ shutil.rmtree(target_dir)
78
+ print(f"Blueprint '{blueprint_name}' deleted successfully.")
79
+ else:
80
+ print(f"Error: Blueprint '{blueprint_name}' does not exist.")
81
+ sys.exit(1)
82
+
83
+ def run_blueprint(blueprint_name):
84
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
85
+ blueprint_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
86
+ if not os.path.exists(blueprint_file):
87
+ print(f"Error: Blueprint file not found for '{blueprint_name}'. Install it using 'swarm-cli add <path>'.")
88
+ sys.exit(1)
89
+ spec = importlib.util.spec_from_file_location("blueprint_module", blueprint_file)
90
+ if spec is None or spec.loader is None:
91
+ print("Error: Failed to load blueprint module from:", blueprint_file)
92
+ sys.exit(1)
93
+ blueprint = importlib.util.module_from_spec(spec)
94
+ loader = spec.loader
95
+ src_path = os.path.join(os.getcwd(), "src")
96
+ if src_path not in sys.path:
97
+ sys.path.insert(0, src_path)
98
+ loader.exec_module(blueprint)
99
+ if hasattr(blueprint, "main"):
100
+ blueprint.main()
101
+ else:
102
+ print("Error: The blueprint does not have a main() function.")
103
+ sys.exit(1)
104
+
105
+ def install_blueprint(blueprint_name):
106
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
107
+ blueprint_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
108
+ if not os.path.exists(blueprint_file):
109
+ print(f"Error: Blueprint '{blueprint_name}' is not registered. Add it using 'swarm-cli add <path>'.")
110
+ sys.exit(1)
111
+ cli_name = blueprint_name # Use blueprint_name as default cli_name for simplicity
112
+ try:
113
+ PyInstaller.__main__.run([
114
+ blueprint_file,
115
+ "--onefile",
116
+ "--name", cli_name,
117
+ "--distpath", BIN_DIR,
118
+ "--workpath", os.path.join(target_dir, "build"),
119
+ "--specpath", target_dir
120
+ ])
121
+ except KeyboardInterrupt:
122
+ print("Installation aborted by user request.")
123
+ sys.exit(1)
124
+ print(f"Blueprint '{blueprint_name}' installed as CLI utility '{cli_name}' at: {os.path.join(BIN_DIR, cli_name)}")
125
+
126
+ def uninstall_blueprint(blueprint_name, blueprint_only=False, wrapper_only=False):
127
+ target_dir = os.path.join(MANAGED_DIR, blueprint_name)
128
+ blueprint_file = os.path.join(target_dir, f"blueprint_{blueprint_name}.py")
129
+ cli_name = blueprint_name # Default to blueprint_name for uninstall
130
+ cli_path = os.path.join(BIN_DIR, cli_name)
131
+ removed = False
132
+
133
+ if not blueprint_only and not wrapper_only: # Remove both by default
134
+ if os.path.exists(target_dir):
135
+ shutil.rmtree(target_dir)
136
+ print(f"Blueprint '{blueprint_name}' removed from {MANAGED_DIR}.")
137
+ removed = True
138
+ if os.path.exists(cli_path):
139
+ os.remove(cli_path)
140
+ print(f"Wrapper '{cli_name}' removed from {BIN_DIR}.")
141
+ removed = True
142
+ elif blueprint_only:
143
+ if os.path.exists(target_dir):
144
+ shutil.rmtree(target_dir)
145
+ print(f"Blueprint '{blueprint_name}' removed from {MANAGED_DIR}.")
146
+ removed = True
147
+ elif wrapper_only:
148
+ if os.path.exists(cli_path):
149
+ os.remove(cli_path)
150
+ print(f"Wrapper '{cli_name}' removed from {BIN_DIR}.")
151
+ removed = True
152
+
153
+ if not removed:
154
+ print(f"Error: Nothing to uninstall for '{blueprint_name}' with specified options.")
155
+ sys.exit(1)
156
+
157
+ def main():
158
+ os.environ.pop("SWARM_BLUEPRINTS", None)
159
+ parser = argparse.ArgumentParser(
160
+ description="Swarm CLI Launcher\n\nSubcommands:\n"
161
+ " add : Add a blueprint to the managed directory.\n"
162
+ " list : List registered blueprints.\n"
163
+ " delete : Delete a registered blueprint.\n"
164
+ " run : Run a blueprint by name.\n"
165
+ " install : Install a blueprint as a CLI utility with PyInstaller.\n"
166
+ " uninstall : Uninstall a blueprint and/or its CLI wrapper.\n"
167
+ " migrate : Apply Django database migrations.\n"
168
+ " config : Manage swarm configuration (LLM and MCP servers).",
169
+ formatter_class=argparse.RawTextHelpFormatter)
170
+ subparsers = parser.add_subparsers(dest="command", required=True, help="Available subcommands")
171
+
172
+ parser_add = subparsers.add_parser("add", help="Add a blueprint from a file or directory.")
173
+ parser_add.add_argument("source", help="Source blueprint file or directory.")
174
+ parser_add.add_argument("--name", help="Optional blueprint name. If not provided, inferred from filename.")
175
+
176
+ parser_list = subparsers.add_parser("list", help="List registered blueprints.")
177
+
178
+ parser_delete = subparsers.add_parser("delete", help="Delete a registered blueprint by name.")
179
+ parser_delete.add_argument("name", help="Blueprint name to delete.")
180
+
181
+ parser_run = subparsers.add_parser("run", help="Run a blueprint by name.")
182
+ parser_run.add_argument("name", help="Blueprint name to run.")
183
+ parser_run.add_argument("--config", default="~/.swarm/swarm_config.json", help="Path to configuration file.")
184
+
185
+ parser_install = subparsers.add_parser("install", help="Install a blueprint as a CLI utility with PyInstaller.")
186
+ parser_install.add_argument("name", help="Blueprint name to install as a CLI utility.")
187
+
188
+ parser_uninstall = subparsers.add_parser("uninstall", help="Uninstall a blueprint and/or its CLI wrapper.")
189
+ parser_uninstall.add_argument("name", help="Blueprint name to uninstall.")
190
+ parser_uninstall.add_argument("--blueprint-only", action="store_true", help="Remove only the blueprint directory.")
191
+ parser_uninstall.add_argument("--wrapper-only", action="store_true", help="Remove only the CLI wrapper.")
192
+
193
+ parser_migrate = subparsers.add_parser("migrate", help="Apply Django database migrations.")
194
+
195
+ parser_config = subparsers.add_parser("config", help="Manage swarm configuration (LLM and MCP servers).")
196
+ parser_config.add_argument("action", choices=["add", "list", "remove"], help="Action to perform on configuration")
197
+ parser_config.add_argument("--section", required=True, choices=["llm", "mcpServers"], help="Configuration section to manage")
198
+ parser_config.add_argument("--name", help="Name of the configuration entry (required for add and remove)")
199
+ parser_config.add_argument("--json", help="JSON string for configuration entry (required for add)")
200
+ parser_config.add_argument("--config", default="~/.swarm/swarm_config.json", help="Path to configuration file")
201
+
202
+ args = parser.parse_args()
203
+ ensure_managed_dir()
204
+
205
+ if args.command == "add":
206
+ add_blueprint(args.source, args.name)
207
+ elif args.command == "list":
208
+ list_blueprints()
209
+ elif args.command == "delete":
210
+ delete_blueprint(args.name)
211
+ elif args.command == "run":
212
+ config_path = os.path.expanduser(args.config)
213
+ if not os.path.exists(config_path):
214
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
215
+ default_config = {"llm": {}, "mcpServers": {}}
216
+ with open(config_path, 'w') as f:
217
+ json.dump(default_config, f, indent=4)
218
+ print("Default config file created at:", config_path)
219
+ run_blueprint(args.name)
220
+ elif args.command == "install":
221
+ install_blueprint(args.name)
222
+ elif args.command == "uninstall":
223
+ uninstall_blueprint(args.name, args.blueprint_only, args.wrapper_only)
224
+ elif args.command == "migrate":
225
+ try:
226
+ subprocess.run(["python", "manage.py", "migrate"], check=True)
227
+ print("Migrations applied successfully.")
228
+ except subprocess.CalledProcessError as e:
229
+ print("Error applying migrations:", e)
230
+ sys.exit(1)
231
+ elif args.command == "config":
232
+ config_path = os.path.expanduser(args.config)
233
+ if not os.path.exists(config_path):
234
+ default_conf = {"llm": {}, "mcpServers": {}}
235
+ os.makedirs(os.path.dirname(config_path), exist_ok=True)
236
+ with open(config_path, "w") as f:
237
+ json.dump(default_conf, f, indent=4)
238
+ print("Default config file created at:", config_path)
239
+ config = default_conf
240
+ else:
241
+ try:
242
+ with open(config_path, "r") as f:
243
+ config = json.load(f)
244
+ except json.JSONDecodeError:
245
+ print("Error: Invalid configuration file.")
246
+ sys.exit(1)
247
+ section = args.section
248
+ if args.action == "list":
249
+ entries = config.get(section, {})
250
+ if entries:
251
+ print(f"Entries in {section}:")
252
+ for key, value in entries.items():
253
+ print(f" - {key}: {json.dumps(value, indent=4)}")
254
+ else:
255
+ print(f"No entries found in {section}.")
256
+ elif args.action == "add":
257
+ if args.section == "mcpServers" and not args.name:
258
+ if not args.json:
259
+ print("Error: --json is required for adding an mcpServers block when --name is omitted.")
260
+ sys.exit(1)
261
+ try:
262
+ update_data = json.loads(args.json)
263
+ except json.JSONDecodeError:
264
+ print("Error: --json must be a valid JSON string.")
265
+ sys.exit(1)
266
+ if "mcpServers" not in update_data:
267
+ print("Error: JSON block must contain 'mcpServers' key for merging.")
268
+ sys.exit(1)
269
+ config.setdefault("mcpServers", {})
270
+ config["mcpServers"].update(update_data["mcpServers"])
271
+ with open(config_path, "w") as f:
272
+ json.dump(config, f, indent=4)
273
+ print("MCP servers updated in configuration.")
274
+ else:
275
+ if not args.name or not args.json:
276
+ print("Error: --name and --json are required for adding an entry.")
277
+ sys.exit(1)
278
+ try:
279
+ entry_data = json.loads(args.json)
280
+ except json.JSONDecodeError:
281
+ print("Error: --json must be a valid JSON string.")
282
+ sys.exit(1)
283
+ config.setdefault(section, {})[args.name] = entry_data
284
+ with open(config_path, "w") as f:
285
+ json.dump(config, f, indent=4)
286
+ print(f"Entry '{args.name}' added to {section} in configuration.")
287
+ elif args.action == "remove":
288
+ if not args.name:
289
+ print("Error: --name is required for removing an entry.")
290
+ sys.exit(1)
291
+ if args.name in config.get(section, {}):
292
+ del config[section][args.name]
293
+ with open(config_path, "w") as f:
294
+ json.dump(config, f, indent=4)
295
+ print(f"Entry '{args.name}' removed from {section} in configuration.")
296
+ else:
297
+ print(f"Error: Entry '{args.name}' not found in {section}.")
298
+ sys.exit(1)
299
+ else:
300
+ parser.print_help()
301
+ sys.exit(1)
10
302
 
11
303
  if __name__ == "__main__":
12
- app()
304
+ main()
@@ -0,0 +1,195 @@
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, Union, AsyncGenerator # Added AsyncGenerator
12
+ from collections import defaultdict
13
+
14
+ import asyncio
15
+ from openai import AsyncOpenAI, OpenAIError
16
+ # Make sure ChatCompletionMessage is correctly imported if it's defined elsewhere
17
+ # Assuming it might be part of the base model or a common types module
18
+ # For now, let's assume it's implicitly handled or use a dict directly
19
+ # from ..types import ChatCompletionMessage, Agent # If defined in types
20
+ from ..types import Agent # Import Agent
21
+ from ..utils.redact import redact_sensitive_data
22
+ from ..utils.general_utils import serialize_datetime
23
+ from ..utils.message_utils import filter_duplicate_system_messages, update_null_content
24
+ from ..utils.context_utils import get_token_count, truncate_message_history
25
+ from ..utils.message_sequence import repair_message_payload
26
+
27
+ # Configure module-level logging
28
+ logger = logging.getLogger(__name__)
29
+ logger.setLevel(logging.DEBUG)
30
+ if not logger.handlers:
31
+ stream_handler = logging.StreamHandler()
32
+ formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s - %(message)s")
33
+ stream_handler.setFormatter(formatter)
34
+ logger.addHandler(stream_handler)
35
+
36
+
37
+ async def get_chat_completion(
38
+ client: AsyncOpenAI,
39
+ agent: Agent,
40
+ history: List[Dict[str, Any]],
41
+ context_variables: dict,
42
+ current_llm_config: Dict[str, Any],
43
+ max_context_tokens: int,
44
+ max_context_messages: int,
45
+ tools: Optional[List[Dict[str, Any]]] = None, # <-- Added tools parameter
46
+ tool_choice: Optional[str] = "auto", # <-- Added tool_choice parameter
47
+ model_override: Optional[str] = None,
48
+ stream: bool = False,
49
+ debug: bool = False
50
+ ) -> Union[Dict[str, Any], AsyncGenerator[Any, None]]: # Adjusted return type hint
51
+ """
52
+ Retrieve a chat completion from the LLM for the given agent and history.
53
+
54
+ Args:
55
+ client: AsyncOpenAI client instance.
56
+ agent: The agent processing the completion.
57
+ history: List of previous messages in the conversation.
58
+ context_variables: Variables to include in the agent's context.
59
+ current_llm_config: Current LLM configuration dictionary.
60
+ max_context_tokens: Maximum token limit for context.
61
+ max_context_messages: Maximum message limit for context.
62
+ tools: Optional list of tools in OpenAI format.
63
+ tool_choice: Tool choice mode (e.g., "auto", "none").
64
+ model_override: Optional model to use instead of default.
65
+ stream: If True, stream the response; otherwise, return complete.
66
+ debug: If True, log detailed debugging information.
67
+
68
+ Returns:
69
+ Union[Dict[str, Any], AsyncGenerator[Any, None]]: The LLM's response message (as dict) or stream.
70
+ """
71
+ if not agent:
72
+ logger.error("Cannot generate chat completion: Agent is None")
73
+ raise ValueError("Agent is required")
74
+
75
+ logger.debug(f"Generating chat completion for agent '{agent.name}'")
76
+ active_model = model_override or current_llm_config.get("model", "default")
77
+ client_kwargs = {
78
+ "api_key": current_llm_config.get("api_key"),
79
+ "base_url": current_llm_config.get("base_url")
80
+ }
81
+ client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
82
+ redacted_kwargs = redact_sensitive_data(client_kwargs, sensitive_keys=["api_key"])
83
+ logger.debug(f"Using client with model='{active_model}', base_url='{client_kwargs.get('base_url', 'default')}', api_key={redacted_kwargs['api_key']}")
84
+
85
+ context_variables = defaultdict(str, context_variables)
86
+ instructions = agent.instructions(context_variables) if callable(agent.instructions) else agent.instructions
87
+ if not isinstance(instructions, str):
88
+ logger.warning(f"Invalid instructions type for '{agent.name}': {type(instructions)}. Converting to string.")
89
+ instructions = str(instructions)
90
+ messages = repair_message_payload([{"role": "system", "content": instructions}], debug=debug)
91
+
92
+ if not isinstance(history, list):
93
+ logger.error(f"Invalid history type for '{agent.name}': {type(history)}. Expected list.")
94
+ history = []
95
+ seen_ids = set()
96
+ for msg in history:
97
+ msg_id = msg.get("id", hash(json.dumps(msg, sort_keys=True, default=serialize_datetime)))
98
+ if msg_id not in seen_ids:
99
+ seen_ids.add(msg_id)
100
+ if "tool_calls" in msg and msg["tool_calls"] is not None and not isinstance(msg["tool_calls"], list):
101
+ logger.warning(f"Invalid tool_calls in history for '{msg.get('sender', 'unknown')}': {msg['tool_calls']}. Setting to None.")
102
+ msg["tool_calls"] = None
103
+ # Ensure content: None becomes content: "" for API compatibility
104
+ if "content" in msg and msg["content"] is None:
105
+ msg["content"] = ""
106
+ messages.append(msg)
107
+ messages = filter_duplicate_system_messages(messages)
108
+ messages = truncate_message_history(messages, active_model, max_context_tokens, max_context_messages)
109
+ messages = repair_message_payload(messages, debug=debug) # Ensure tool calls are paired post-truncation
110
+ # Final content None -> "" check after repair
111
+ messages = update_null_content(messages)
112
+
113
+ logger.debug(f"Prepared {len(messages)} messages for '{agent.name}'")
114
+ if debug:
115
+ logger.debug(f"Messages: {json.dumps(messages, indent=2, default=str)}")
116
+
117
+ create_params = {
118
+ "model": active_model,
119
+ "messages": messages,
120
+ "stream": stream,
121
+ "temperature": current_llm_config.get("temperature", 0.7),
122
+ # --- Pass tools and tool_choice ---
123
+ "tools": tools if tools else None,
124
+ "tool_choice": tool_choice if tools else None, # Only set tool_choice if tools are provided
125
+ }
126
+ if getattr(agent, "response_format", None):
127
+ create_params["response_format"] = agent.response_format
128
+ create_params = {k: v for k, v in create_params.items() if v is not None} # Clean None values
129
+
130
+ tool_info_log = f", tools_count={len(tools)}" if tools else ", tools=None"
131
+ logger.debug(f"Chat completion params: model='{active_model}', messages_count={len(messages)}, stream={stream}{tool_info_log}, tool_choice={create_params.get('tool_choice')}")
132
+
133
+ try:
134
+ logger.debug(f"Calling OpenAI API for '{agent.name}' with model='{active_model}'")
135
+ # Temporary workaround for potential env var conflicts if client doesn't isolate well
136
+ prev_openai_api_key = os.environ.pop("OPENAI_API_KEY", None)
137
+ try:
138
+ completion = await client.chat.completions.create(**create_params)
139
+ if stream:
140
+ return completion # Return stream object directly
141
+
142
+ # --- Handle Non-Streaming Response ---
143
+ if completion.choices and len(completion.choices) > 0 and completion.choices[0].message:
144
+ message_dict = completion.choices[0].message.model_dump(exclude_none=True)
145
+ log_msg = message_dict.get("content", "No content")[:50] if message_dict.get("content") else "No content"
146
+ if message_dict.get("tool_calls"): log_msg += f" (+{len(message_dict['tool_calls'])} tool calls)"
147
+ logger.debug(f"OpenAI completion received for '{agent.name}': {log_msg}...")
148
+ return message_dict # Return the message dictionary
149
+ else:
150
+ logger.warning(f"No valid message in completion for '{agent.name}'")
151
+ return {"role": "assistant", "content": "No response generated"} # Return dict
152
+ finally:
153
+ if prev_openai_api_key is not None:
154
+ os.environ["OPENAI_API_KEY"] = prev_openai_api_key
155
+ except OpenAIError as e:
156
+ logger.error(f"Chat completion failed for '{agent.name}': {e}")
157
+ raise
158
+ except Exception as e: # Catch broader errors during API call
159
+ logger.error(f"Unexpected error during chat completion for '{agent.name}': {e}", exc_info=True)
160
+ raise # Re-raise
161
+
162
+
163
+ async def get_chat_completion_message(
164
+ client: AsyncOpenAI,
165
+ agent: Agent,
166
+ history: List[Dict[str, Any]],
167
+ context_variables: dict,
168
+ current_llm_config: Dict[str, Any],
169
+ max_context_tokens: int,
170
+ max_context_messages: int,
171
+ tools: Optional[List[Dict[str, Any]]] = None, # <-- Added tools
172
+ tool_choice: Optional[str] = "auto", # <-- Added tool_choice
173
+ model_override: Optional[str] = None,
174
+ stream: bool = False,
175
+ debug: bool = False
176
+ ) -> Union[Dict[str, Any], AsyncGenerator[Any, None]]: # Return dict or stream
177
+ """
178
+ Wrapper to retrieve and validate a chat completion message (returns dict or stream).
179
+
180
+ Args:
181
+ Same as get_chat_completion.
182
+
183
+ Returns:
184
+ Union[Dict[str, Any], AsyncGenerator[Any, None]]: Validated LLM response message as dict or the stream.
185
+ """
186
+ logger.debug(f"Fetching chat completion message for '{agent.name}'")
187
+ completion_result = await get_chat_completion(
188
+ client, agent, history, context_variables, current_llm_config,
189
+ max_context_tokens, max_context_messages,
190
+ tools=tools, tool_choice=tool_choice, # Pass through
191
+ model_override=model_override, stream=stream, debug=debug
192
+ )
193
+ # If streaming, completion_result is already the generator
194
+ # If not streaming, it's the message dictionary
195
+ return completion_result