open-swarm 0.1.1745275181__py3-none-any.whl → 0.1.1748636295__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- open_swarm-0.1.1748636295.dist-info/METADATA +257 -0
- open_swarm-0.1.1748636295.dist-info/RECORD +89 -0
- {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/WHEEL +2 -1
- open_swarm-0.1.1748636295.dist-info/entry_points.txt +3 -0
- open_swarm-0.1.1748636295.dist-info/top_level.txt +1 -0
- swarm/__init__.py +2 -0
- swarm/agent/agent.py +49 -0
- swarm/auth.py +48 -113
- swarm/consumers.py +0 -19
- swarm/core.py +411 -0
- swarm/extensions/blueprint/__init__.py +16 -30
- swarm/extensions/blueprint/agent_utils.py +45 -0
- swarm/extensions/blueprint/blueprint_base.py +562 -0
- swarm/extensions/blueprint/blueprint_discovery.py +112 -0
- swarm/extensions/blueprint/django_utils.py +79 -181
- swarm/extensions/blueprint/interactive_mode.py +72 -67
- swarm/extensions/blueprint/output_utils.py +82 -0
- swarm/{core → extensions/blueprint}/spinner.py +21 -30
- swarm/extensions/cli/cli_args.py +0 -6
- swarm/extensions/cli/commands/blueprint_management.py +9 -47
- swarm/extensions/cli/commands/config_management.py +6 -5
- swarm/extensions/cli/commands/edit_config.py +7 -16
- swarm/extensions/cli/commands/list_blueprints.py +1 -1
- swarm/extensions/cli/commands/validate_env.py +4 -11
- swarm/extensions/cli/commands/validate_envvars.py +6 -6
- swarm/extensions/cli/interactive_shell.py +2 -16
- swarm/extensions/config/config_loader.py +345 -107
- swarm/{core → extensions/config}/config_manager.py +38 -50
- swarm/{core → extensions/config}/server_config.py +0 -32
- swarm/extensions/launchers/build_launchers.py +14 -0
- swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
- swarm/extensions/launchers/swarm_api.py +64 -8
- swarm/extensions/launchers/swarm_cli.py +300 -8
- swarm/extensions/mcp/__init__.py +1 -0
- swarm/extensions/mcp/cache_utils.py +32 -0
- swarm/extensions/mcp/mcp_client.py +233 -0
- swarm/extensions/mcp/mcp_tool_provider.py +135 -0
- swarm/extensions/mcp/mcp_utils.py +260 -0
- swarm/llm/chat_completion.py +166 -0
- swarm/serializers.py +5 -96
- swarm/settings.py +133 -85
- swarm/types.py +91 -0
- swarm/urls.py +74 -57
- swarm/utils/context_utils.py +4 -10
- swarm/utils/general_utils.py +0 -21
- swarm/utils/redact.py +36 -23
- swarm/views/api_views.py +39 -48
- swarm/views/chat_views.py +76 -236
- swarm/views/core_views.py +87 -80
- swarm/views/model_views.py +121 -64
- swarm/views/utils.py +439 -65
- swarm/views/web_views.py +2 -2
- open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
- open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
- open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
- swarm/blueprints/README.md +0 -68
- swarm/blueprints/blueprint_audit_status.json +0 -27
- swarm/blueprints/chatbot/README.md +0 -40
- swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
- swarm/blueprints/chatbot/metadata.json +0 -23
- swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
- swarm/blueprints/chucks_angels/README.md +0 -11
- swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
- swarm/blueprints/chucks_angels/test_basic.py +0 -3
- swarm/blueprints/codey/CODEY.md +0 -15
- swarm/blueprints/codey/README.md +0 -115
- swarm/blueprints/codey/blueprint_codey.py +0 -1072
- swarm/blueprints/codey/codey_cli.py +0 -373
- swarm/blueprints/codey/instructions.md +0 -17
- swarm/blueprints/codey/metadata.json +0 -23
- swarm/blueprints/common/operation_box_utils.py +0 -83
- swarm/blueprints/digitalbutlers/README.md +0 -11
- swarm/blueprints/digitalbutlers/__init__.py +0 -1
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
- swarm/blueprints/digitalbutlers/test_basic.py +0 -3
- swarm/blueprints/divine_code/README.md +0 -3
- swarm/blueprints/divine_code/__init__.py +0 -10
- swarm/blueprints/divine_code/apps.py +0 -11
- swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
- swarm/blueprints/django_chat/apps.py +0 -6
- swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
- swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
- swarm/blueprints/django_chat/urls.py +0 -8
- swarm/blueprints/django_chat/views.py +0 -32
- swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
- swarm/blueprints/flock/README.md +0 -11
- swarm/blueprints/flock/__init__.py +0 -8
- swarm/blueprints/flock/blueprint_flock.py +0 -7
- swarm/blueprints/flock/test_basic.py +0 -3
- swarm/blueprints/geese/README.md +0 -10
- swarm/blueprints/geese/__init__.py +0 -8
- swarm/blueprints/geese/blueprint_geese.py +0 -384
- swarm/blueprints/geese/geese_cli.py +0 -102
- swarm/blueprints/jeeves/README.md +0 -41
- swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
- swarm/blueprints/jeeves/jeeves_cli.py +0 -55
- swarm/blueprints/jeeves/metadata.json +0 -24
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
- swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
- swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
- swarm/blueprints/poets/blueprint_poets.py +0 -546
- swarm/blueprints/poets/poets_cli.py +0 -23
- swarm/blueprints/rue_code/README.md +0 -8
- swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
- swarm/blueprints/rue_code/rue_code_cli.py +0 -43
- swarm/blueprints/stewie/apps.py +0 -12
- swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
- swarm/blueprints/stewie/models.py +0 -19
- swarm/blueprints/stewie/serializers.py +0 -10
- swarm/blueprints/stewie/settings.py +0 -17
- swarm/blueprints/stewie/urls.py +0 -11
- swarm/blueprints/stewie/views.py +0 -26
- swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
- swarm/blueprints/whinge_surf/README.md +0 -22
- swarm/blueprints/whinge_surf/__init__.py +0 -1
- swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
- swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
- swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
- swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
- swarm/blueprints/zeus/__init__.py +0 -2
- swarm/blueprints/zeus/apps.py +0 -4
- swarm/blueprints/zeus/blueprint_zeus.py +0 -270
- swarm/blueprints/zeus/zeus_cli.py +0 -13
- swarm/cli/async_input.py +0 -65
- swarm/cli/async_input_demo.py +0 -32
- swarm/core/agent_utils.py +0 -21
- swarm/core/blueprint_base.py +0 -769
- swarm/core/blueprint_discovery.py +0 -125
- swarm/core/blueprint_runner.py +0 -59
- swarm/core/blueprint_ux.py +0 -109
- swarm/core/build_launchers.py +0 -15
- swarm/core/cli/__init__.py +0 -1
- swarm/core/cli/commands/__init__.py +0 -1
- swarm/core/cli/commands/blueprint_management.py +0 -7
- swarm/core/cli/interactive_shell.py +0 -14
- swarm/core/cli/main.py +0 -50
- swarm/core/cli/utils/__init__.py +0 -1
- swarm/core/cli/utils/discover_commands.py +0 -18
- swarm/core/config_loader.py +0 -122
- swarm/core/output_utils.py +0 -193
- swarm/core/session_logger.py +0 -42
- swarm/core/slash_commands.py +0 -89
- swarm/core/swarm_api.py +0 -68
- swarm/core/swarm_cli.py +0 -216
- swarm/core/utils/__init__.py +0 -0
- swarm/extensions/blueprint/cli_handler.py +0 -197
- swarm/extensions/blueprint/runnable_blueprint.py +0 -42
- swarm/extensions/cli/utils/__init__.py +0 -1
- swarm/extensions/cli/utils/async_input.py +0 -46
- swarm/extensions/cli/utils/prompt_user.py +0 -3
- swarm/management/__init__.py +0 -0
- swarm/management/commands/__init__.py +0 -0
- swarm/management/commands/runserver.py +0 -58
- swarm/middleware.py +0 -65
- swarm/permissions.py +0 -38
- swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
- swarm/static/contrib/markedjs/marked.min.js +0 -6
- swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
- swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
- swarm/static/contrib/tabler-icons/archive.svg +0 -21
- swarm/static/contrib/tabler-icons/artboard.svg +0 -27
- swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
- swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
- swarm/static/contrib/tabler-icons/carambola.svg +0 -19
- swarm/static/contrib/tabler-icons/copy.svg +0 -20
- swarm/static/contrib/tabler-icons/download.svg +0 -21
- swarm/static/contrib/tabler-icons/edit.svg +0 -21
- swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
- swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
- swarm/static/contrib/tabler-icons/headset.svg +0 -22
- swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
- swarm/static/contrib/tabler-icons/message-star.svg +0 -22
- swarm/static/contrib/tabler-icons/message-x.svg +0 -23
- swarm/static/contrib/tabler-icons/message.svg +0 -21
- swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
- swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
- swarm/static/contrib/tabler-icons/robot.svg +0 -26
- swarm/static/contrib/tabler-icons/search.svg +0 -19
- swarm/static/contrib/tabler-icons/settings.svg +0 -20
- swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
- swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
- swarm/static/css/dropdown.css +0 -22
- swarm/static/htmx/htmx.min.js +0 -0
- swarm/static/js/dropdown.js +0 -23
- swarm/static/rest_mode/css/base.css +0 -470
- swarm/static/rest_mode/css/chat-history.css +0 -286
- swarm/static/rest_mode/css/chat.css +0 -251
- swarm/static/rest_mode/css/chatbot.css +0 -74
- swarm/static/rest_mode/css/chatgpt.css +0 -62
- swarm/static/rest_mode/css/colors/corporate.css +0 -74
- swarm/static/rest_mode/css/colors/pastel.css +0 -81
- swarm/static/rest_mode/css/colors/tropical.css +0 -82
- swarm/static/rest_mode/css/general.css +0 -142
- swarm/static/rest_mode/css/layout.css +0 -167
- swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
- swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
- swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
- swarm/static/rest_mode/css/messages.css +0 -84
- swarm/static/rest_mode/css/messenger.css +0 -135
- swarm/static/rest_mode/css/settings.css +0 -91
- swarm/static/rest_mode/css/simple.css +0 -44
- swarm/static/rest_mode/css/slack.css +0 -58
- swarm/static/rest_mode/css/style.css +0 -156
- swarm/static/rest_mode/css/theme.css +0 -30
- swarm/static/rest_mode/css/toast.css +0 -40
- swarm/static/rest_mode/js/auth.js +0 -9
- swarm/static/rest_mode/js/blueprint.js +0 -41
- swarm/static/rest_mode/js/blueprintUtils.js +0 -12
- swarm/static/rest_mode/js/chatLogic.js +0 -79
- swarm/static/rest_mode/js/debug.js +0 -63
- swarm/static/rest_mode/js/events.js +0 -98
- swarm/static/rest_mode/js/main.js +0 -19
- swarm/static/rest_mode/js/messages.js +0 -264
- swarm/static/rest_mode/js/messengerLogic.js +0 -355
- swarm/static/rest_mode/js/modules/apiService.js +0 -84
- swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
- swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
- swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
- swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
- swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
- swarm/static/rest_mode/js/modules/state.js +0 -7
- swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
- swarm/static/rest_mode/js/modules/validation.js +0 -23
- swarm/static/rest_mode/js/rendering.js +0 -119
- swarm/static/rest_mode/js/settings.js +0 -130
- swarm/static/rest_mode/js/sidebar.js +0 -94
- swarm/static/rest_mode/js/simpleLogic.js +0 -37
- swarm/static/rest_mode/js/slackLogic.js +0 -66
- swarm/static/rest_mode/js/splash.js +0 -76
- swarm/static/rest_mode/js/theme.js +0 -111
- swarm/static/rest_mode/js/toast.js +0 -36
- swarm/static/rest_mode/js/ui.js +0 -265
- swarm/static/rest_mode/js/validation.js +0 -57
- swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
- swarm/static/rest_mode/svg/arrow_down.svg +0 -5
- swarm/static/rest_mode/svg/arrow_left.svg +0 -5
- swarm/static/rest_mode/svg/arrow_right.svg +0 -5
- swarm/static/rest_mode/svg/arrow_up.svg +0 -5
- swarm/static/rest_mode/svg/attach.svg +0 -8
- swarm/static/rest_mode/svg/avatar.svg +0 -7
- swarm/static/rest_mode/svg/canvas.svg +0 -6
- swarm/static/rest_mode/svg/chat_history.svg +0 -4
- swarm/static/rest_mode/svg/close.svg +0 -5
- swarm/static/rest_mode/svg/copy.svg +0 -4
- swarm/static/rest_mode/svg/dark_mode.svg +0 -3
- swarm/static/rest_mode/svg/edit.svg +0 -5
- swarm/static/rest_mode/svg/layout.svg +0 -9
- swarm/static/rest_mode/svg/logo.svg +0 -29
- swarm/static/rest_mode/svg/logout.svg +0 -5
- swarm/static/rest_mode/svg/mobile.svg +0 -5
- swarm/static/rest_mode/svg/new_chat.svg +0 -4
- swarm/static/rest_mode/svg/not_visible.svg +0 -5
- swarm/static/rest_mode/svg/plus.svg +0 -7
- swarm/static/rest_mode/svg/run_code.svg +0 -6
- swarm/static/rest_mode/svg/save.svg +0 -4
- swarm/static/rest_mode/svg/search.svg +0 -6
- swarm/static/rest_mode/svg/settings.svg +0 -4
- swarm/static/rest_mode/svg/speaker.svg +0 -5
- swarm/static/rest_mode/svg/stop.svg +0 -6
- swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
- swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
- swarm/static/rest_mode/svg/toggle_off.svg +0 -6
- swarm/static/rest_mode/svg/toggle_on.svg +0 -6
- swarm/static/rest_mode/svg/trash.svg +0 -10
- swarm/static/rest_mode/svg/undo.svg +0 -3
- swarm/static/rest_mode/svg/visible.svg +0 -8
- swarm/static/rest_mode/svg/voice.svg +0 -10
- swarm/templates/account/login.html +0 -22
- swarm/templates/account/signup.html +0 -32
- swarm/templates/base.html +0 -30
- swarm/templates/chat.html +0 -43
- swarm/templates/index.html +0 -35
- swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
- swarm/templates/rest_mode/components/header.html +0 -45
- swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
- swarm/templates/rest_mode/components/settings_dialog.html +0 -97
- swarm/templates/rest_mode/components/splash_screen.html +0 -7
- swarm/templates/rest_mode/components/top_bar.html +0 -28
- swarm/templates/rest_mode/message_ui.html +0 -50
- swarm/templates/rest_mode/slackbot.html +0 -30
- swarm/templates/simple_blueprint_page.html +0 -24
- swarm/templates/websocket_partials/final_system_message.html +0 -3
- swarm/templates/websocket_partials/system_message.html +0 -4
- swarm/templates/websocket_partials/user_message.html +0 -5
- swarm/utils/ansi_box.py +0 -34
- swarm/utils/disable_tracing.py +0 -38
- swarm/utils/log_utils.py +0 -63
- swarm/utils/openai_patch.py +0 -33
- swarm/ux/ansi_box.py +0 -43
- swarm/ux/spinner.py +0 -53
- {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/licenses/LICENSE +0 -0
- /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
- /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
- /swarm/{core → extensions/config}/setup_wizard.py +0 -0
- /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
- /swarm/{core → extensions/config}/utils/logger.py +0 -0
- /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
swarm/core/blueprint_base.py
DELETED
@@ -1,769 +0,0 @@
|
|
1
|
-
# --- REMOVE noisy debug/framework prints unless SWARM_DEBUG=1 ---
|
2
|
-
import os
|
3
|
-
from swarm.utils.general_utils import is_debug_enabled
|
4
|
-
|
5
|
-
def _should_debug():
|
6
|
-
# Standardize debug detection: SWARM_DEBUG, SWARM_LOGLEVEL, LOGLEVEL, LOG_LEVEL, DEBUG
|
7
|
-
import os
|
8
|
-
# Highest precedence: explicit SWARM_DEBUG=1 or true
|
9
|
-
debug_env = os.environ.get("SWARM_DEBUG")
|
10
|
-
if debug_env is not None:
|
11
|
-
return debug_env.lower() in ("1", "true", "yes", "on")
|
12
|
-
# Next: SWARM_LOGLEVEL or LOGLEVEL or LOG_LEVEL
|
13
|
-
for var in ("SWARM_LOGLEVEL", "LOGLEVEL", "LOG_LEVEL"):
|
14
|
-
val = os.environ.get(var)
|
15
|
-
if val and val.upper() == "DEBUG":
|
16
|
-
return True
|
17
|
-
# Next: DEBUG=1 or true
|
18
|
-
debug_std = os.environ.get("DEBUG")
|
19
|
-
if debug_std is not None:
|
20
|
-
return debug_std.lower() in ("1", "true", "yes", "on")
|
21
|
-
return False
|
22
|
-
|
23
|
-
def _debug_print(*args, **kwargs):
|
24
|
-
if _should_debug():
|
25
|
-
print(*args, **kwargs)
|
26
|
-
|
27
|
-
def _framework_print(*args, **kwargs):
|
28
|
-
if _should_debug():
|
29
|
-
print(*args, **kwargs)
|
30
|
-
|
31
|
-
# --- Content for src/swarm/extensions/blueprint/blueprint_base.py ---
|
32
|
-
import logging
|
33
|
-
import json
|
34
|
-
from abc import ABC, abstractmethod
|
35
|
-
from typing import Dict, Any, Optional, List, AsyncGenerator
|
36
|
-
from pathlib import Path
|
37
|
-
from django.apps import apps # Import Django apps registry
|
38
|
-
|
39
|
-
# Keep the function import
|
40
|
-
from swarm.core.config_loader import _substitute_env_vars
|
41
|
-
|
42
|
-
from openai import AsyncOpenAI
|
43
|
-
from agents import set_default_openai_client
|
44
|
-
|
45
|
-
logger = logging.getLogger(__name__)
|
46
|
-
from rich.console import Console
|
47
|
-
import traceback
|
48
|
-
|
49
|
-
# --- PATCH: Suppress OpenAI tracing/telemetry errors if using LiteLLM/custom endpoint ---
|
50
|
-
import logging
|
51
|
-
import os
|
52
|
-
if os.environ.get("LITELLM_BASE_URL") or os.environ.get("OPENAI_BASE_URL"):
|
53
|
-
# Silence openai.agents tracing/telemetry errors
|
54
|
-
logging.getLogger("openai.agents").setLevel(logging.CRITICAL)
|
55
|
-
try:
|
56
|
-
import openai.agents.tracing
|
57
|
-
openai.agents.tracing.TracingClient = lambda *a, **kw: None
|
58
|
-
except Exception:
|
59
|
-
pass
|
60
|
-
|
61
|
-
# --- Spinner/Status Message Enhancements ---
|
62
|
-
# To be used by all blueprints for consistent UX
|
63
|
-
import itertools
|
64
|
-
import sys
|
65
|
-
import threading
|
66
|
-
import time
|
67
|
-
|
68
|
-
class Spinner:
|
69
|
-
def __init__(self, message_sequence=None, interval=0.3, slow_threshold=10):
|
70
|
-
self.message_sequence = message_sequence or ['Generating.', 'Generating..', 'Generating...', 'Running...']
|
71
|
-
self.interval = interval
|
72
|
-
self.slow_threshold = slow_threshold # seconds before 'Taking longer than expected'
|
73
|
-
self._stop_event = threading.Event()
|
74
|
-
self._thread = None
|
75
|
-
self._start_time = None
|
76
|
-
|
77
|
-
def start(self):
|
78
|
-
self._stop_event.clear()
|
79
|
-
self._start_time = time.time()
|
80
|
-
self._thread = threading.Thread(target=self._spin)
|
81
|
-
self._thread.start()
|
82
|
-
|
83
|
-
def _spin(self):
|
84
|
-
for msg in itertools.cycle(self.message_sequence):
|
85
|
-
if self._stop_event.is_set():
|
86
|
-
break
|
87
|
-
elapsed = time.time() - self._start_time
|
88
|
-
if elapsed > self.slow_threshold:
|
89
|
-
sys.stdout.write('\rGenerating... Taking longer than expected ')
|
90
|
-
else:
|
91
|
-
sys.stdout.write(f'\r{msg} ')
|
92
|
-
sys.stdout.flush()
|
93
|
-
time.sleep(self.interval)
|
94
|
-
sys.stdout.write('\r')
|
95
|
-
sys.stdout.flush()
|
96
|
-
|
97
|
-
def stop(self, final_message=''):
|
98
|
-
self._stop_event.set()
|
99
|
-
if self._thread:
|
100
|
-
self._thread.join()
|
101
|
-
if final_message:
|
102
|
-
sys.stdout.write(f'\r{final_message}\n')
|
103
|
-
sys.stdout.flush()
|
104
|
-
|
105
|
-
# Usage Example (to be called in blueprints):
|
106
|
-
# spinner = Spinner()
|
107
|
-
# spinner.start()
|
108
|
-
# ... do work ...
|
109
|
-
# spinner.stop('Done!')
|
110
|
-
|
111
|
-
def configure_openai_client_from_env():
|
112
|
-
"""
|
113
|
-
Framework-level function: Always instantiate and set the default OpenAI client.
|
114
|
-
Prints out the config being used for debug.
|
115
|
-
"""
|
116
|
-
import os
|
117
|
-
from agents import set_default_openai_client
|
118
|
-
from openai import AsyncOpenAI
|
119
|
-
base_url = os.environ.get("LITELLM_BASE_URL") or os.environ.get("OPENAI_BASE_URL")
|
120
|
-
api_key = os.environ.get("LITELLM_API_KEY") or os.environ.get("OPENAI_API_KEY")
|
121
|
-
_debug_print(f"[DEBUG] Using OpenAI client config: base_url={base_url}, api_key={'set' if api_key else 'NOT SET'}")
|
122
|
-
if base_url and api_key:
|
123
|
-
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
|
124
|
-
set_default_openai_client(client)
|
125
|
-
_framework_print(f"[FRAMEWORK] Set default OpenAI client: base_url={base_url}, api_key={'set' if api_key else 'NOT SET'}")
|
126
|
-
else:
|
127
|
-
_framework_print("[FRAMEWORK] WARNING: base_url or api_key missing, OpenAI client not set!")
|
128
|
-
|
129
|
-
configure_openai_client_from_env()
|
130
|
-
|
131
|
-
class BlueprintBase(ABC):
|
132
|
-
"""
|
133
|
-
Abstract base class for all Swarm blueprints.
|
134
|
-
|
135
|
-
Defines the core interface for blueprint initialization and execution.
|
136
|
-
"""
|
137
|
-
enable_terminal_commands: bool = False # By default, terminal command execution is disabled
|
138
|
-
approval_required: bool = False
|
139
|
-
console = Console()
|
140
|
-
session_logger: 'SessionLogger' = None
|
141
|
-
|
142
|
-
def __init__(self, blueprint_id: str, config=None, config_path=None, **kwargs):
|
143
|
-
self.blueprint_id = blueprint_id
|
144
|
-
self.config_path = config_path
|
145
|
-
self._config = config if config is not None else None
|
146
|
-
self._llm_profile_name = None
|
147
|
-
self._llm_profile_data = None
|
148
|
-
self._markdown_output = None
|
149
|
-
self._load_configuration() # Ensure config is loaded during init
|
150
|
-
# Add any additional initialization logic here
|
151
|
-
|
152
|
-
def display_splash_screen(self, animated: bool = False):
|
153
|
-
"""Default splash screen. Subclasses can override for custom CLI/API branding."""
|
154
|
-
console = Console()
|
155
|
-
console.print(f"[bold cyan]Welcome to {self.__class__.__name__}![/]", style="bold")
|
156
|
-
|
157
|
-
def _load_configuration(self):
|
158
|
-
"""
|
159
|
-
Loads blueprint configuration. This method is a stub for compatibility with tests that patch it.
|
160
|
-
In production, configuration is loaded via _load_and_process_config.
|
161
|
-
"""
|
162
|
-
import os
|
163
|
-
import json
|
164
|
-
from pathlib import Path
|
165
|
-
def redact(val):
|
166
|
-
if not isinstance(val, str) or len(val) <= 4:
|
167
|
-
return "****"
|
168
|
-
return val[:2] + "*" * (len(val)-4) + val[-2:]
|
169
|
-
def redact_dict(d):
|
170
|
-
if isinstance(d, dict):
|
171
|
-
return {k: (redact_dict(v) if not (isinstance(v, str) and ("key" in k.lower() or "token" in k.lower() or "secret" in k.lower())) else redact(v)) for k, v in d.items()}
|
172
|
-
elif isinstance(d, list):
|
173
|
-
return [redact_dict(item) for item in d]
|
174
|
-
return d
|
175
|
-
try:
|
176
|
-
if self._config is None:
|
177
|
-
try:
|
178
|
-
# --- Get config from the AppConfig instance (Django) ---
|
179
|
-
app_config_instance = apps.get_app_config('swarm')
|
180
|
-
if not hasattr(app_config_instance, 'config') or not app_config_instance.config:
|
181
|
-
raise ValueError("AppConfig for 'swarm' does not have a valid 'config' attribute.")
|
182
|
-
self._config = app_config_instance.config
|
183
|
-
print("[SWARM_CONFIG_DEBUG] Loaded config from Django AppConfig.")
|
184
|
-
except Exception as e:
|
185
|
-
if _should_debug():
|
186
|
-
logger.warning(f"Falling back to CLI/home config due to error: {e}")
|
187
|
-
# 1. CLI argument (not handled here, handled in cli_handler)
|
188
|
-
# 2. Current working directory (guard against missing CWD)
|
189
|
-
try:
|
190
|
-
cwd_config = Path.cwd() / "swarm_config.json"
|
191
|
-
print(f"[SWARM_CONFIG_DEBUG] Trying: {cwd_config}")
|
192
|
-
except Exception as e:
|
193
|
-
cwd_config = None
|
194
|
-
if _should_debug():
|
195
|
-
logger.warning(f"Unable to determine CWD for config lookup: {e}")
|
196
|
-
if cwd_config and cwd_config.exists():
|
197
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {cwd_config}")
|
198
|
-
with open(cwd_config, 'r') as f:
|
199
|
-
self._config = json.load(f)
|
200
|
-
# 3. XDG_CONFIG_HOME or ~/.config/swarm/swarm_config.json
|
201
|
-
elif os.environ.get("XDG_CONFIG_HOME"):
|
202
|
-
xdg_config = Path(os.environ["XDG_CONFIG_HOME"]) / "swarm" / "swarm_config.json"
|
203
|
-
print(f"[SWARM_CONFIG_DEBUG] Trying: {xdg_config}")
|
204
|
-
if xdg_config.exists():
|
205
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {xdg_config}")
|
206
|
-
with open(xdg_config, 'r') as f:
|
207
|
-
self._config = json.load(f)
|
208
|
-
elif (Path.home() / ".config/swarm/swarm_config.json").exists():
|
209
|
-
home_config = Path.home() / ".config/swarm/swarm_config.json"
|
210
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {home_config}")
|
211
|
-
with open(home_config, 'r') as f:
|
212
|
-
self._config = json.load(f)
|
213
|
-
# 4. Legacy fallback: ~/.swarm/swarm_config.json
|
214
|
-
elif (Path.home() / ".swarm/swarm_config.json").exists():
|
215
|
-
legacy_config = Path.home() / ".swarm/swarm_config.json"
|
216
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {legacy_config}")
|
217
|
-
with open(legacy_config, 'r') as f:
|
218
|
-
self._config = json.load(f)
|
219
|
-
# 5. Fallback: OPENAI_API_KEY envvar
|
220
|
-
elif os.environ.get("OPENAI_API_KEY"):
|
221
|
-
print("[SWARM_CONFIG_DEBUG] No config file found, using OPENAI_API_KEY from env.")
|
222
|
-
self._config = {
|
223
|
-
"llm": {"default": {"provider": "openai", "model": "gpt-3.5-turbo", "api_key": os.environ["OPENAI_API_KEY"]}},
|
224
|
-
"settings": {"default_llm_profile": "default", "default_markdown_output": True},
|
225
|
-
"blueprints": {},
|
226
|
-
"llm_profile": "default",
|
227
|
-
"mcpServers": {}
|
228
|
-
}
|
229
|
-
logger.info("No config file found, using default config with OPENAI_API_KEY for CLI mode.")
|
230
|
-
else:
|
231
|
-
print("[SWARM_CONFIG_DEBUG] No config file found and OPENAI_API_KEY is not set. Using empty config.")
|
232
|
-
self._config = {}
|
233
|
-
logger.warning("No config file found and OPENAI_API_KEY is not set. Using empty config. CLI blueprints may fail if LLM config is required.")
|
234
|
-
if self._config is not None:
|
235
|
-
self._config = _substitute_env_vars(self._config)
|
236
|
-
# Ensure self._config is always a dict
|
237
|
-
if self._config is None:
|
238
|
-
self._config = {}
|
239
|
-
settings_section = self._config.get("settings", {})
|
240
|
-
llm_section = self._config.get("llm", {})
|
241
|
-
|
242
|
-
# --- After config is loaded, set OpenAI client from config if possible ---
|
243
|
-
try:
|
244
|
-
llm_profiles = self._config.get("llm", {})
|
245
|
-
default_profile = llm_profiles.get("default", {})
|
246
|
-
base_url = default_profile.get("base_url")
|
247
|
-
api_key = default_profile.get("api_key")
|
248
|
-
# Expand env vars if present
|
249
|
-
import os
|
250
|
-
if base_url and base_url.startswith("${"):
|
251
|
-
var = base_url[2:-1]
|
252
|
-
base_url = os.environ.get(var, base_url)
|
253
|
-
if api_key and api_key.startswith("${"):
|
254
|
-
var = api_key[2:-1]
|
255
|
-
api_key = os.environ.get(var, api_key)
|
256
|
-
if base_url and api_key:
|
257
|
-
from openai import AsyncOpenAI
|
258
|
-
from agents import set_default_openai_client
|
259
|
-
_debug_print(f"[DEBUG] (config) Setting OpenAI client: base_url={base_url}, api_key={'set' if api_key else 'NOT SET'}")
|
260
|
-
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
|
261
|
-
set_default_openai_client(client)
|
262
|
-
except Exception as e:
|
263
|
-
_debug_print(f"[DEBUG] Failed to set OpenAI client from config: {e}")
|
264
|
-
|
265
|
-
# --- Debug: Print and log redacted config ---
|
266
|
-
redacted_config = redact_dict(self._config)
|
267
|
-
logger.debug(f"Loaded config (redacted): {json.dumps(redacted_config, indent=2)}")
|
268
|
-
|
269
|
-
# --- Process LLM profile name and data ---
|
270
|
-
default_profile = settings_section.get("default_llm_profile") or "default"
|
271
|
-
# Only set self._llm_profile_name if explicitly provided in config
|
272
|
-
if "llm_profile" in self._config:
|
273
|
-
self._llm_profile_name = self._config["llm_profile"]
|
274
|
-
# Do NOT set self._llm_profile_name to default_profile here; let resolution logic handle fallback
|
275
|
-
if "profiles" in llm_section:
|
276
|
-
self._llm_profile_data = llm_section["profiles"].get(self._llm_profile_name, {})
|
277
|
-
else:
|
278
|
-
self._llm_profile_data = llm_section.get(self._llm_profile_name, {})
|
279
|
-
blueprint_specific_settings = self._config.get("blueprints", {}).get(self.blueprint_id, {})
|
280
|
-
global_markdown_setting = settings_section.get("default_markdown_output", True)
|
281
|
-
self._markdown_output = blueprint_specific_settings.get("markdown_output", global_markdown_setting)
|
282
|
-
logger.debug(f"Markdown output for '{self.blueprint_id}': {self._markdown_output}")
|
283
|
-
|
284
|
-
except ValueError as e:
|
285
|
-
logger.error(f"Configuration error for blueprint '{self.blueprint_id}': {e}", exc_info=True)
|
286
|
-
raise
|
287
|
-
except Exception as e:
|
288
|
-
logger.error(f"Unexpected error loading config for blueprint '{self.blueprint_id}': {e}", exc_info=True)
|
289
|
-
raise
|
290
|
-
|
291
|
-
def _load_and_process_config(self):
|
292
|
-
"""Loads the main Swarm config and extracts relevant settings. Falls back to empty config if Django unavailable or not found."""
|
293
|
-
import os
|
294
|
-
import json
|
295
|
-
from pathlib import Path
|
296
|
-
def redact(val):
|
297
|
-
if not isinstance(val, str) or len(val) <= 4:
|
298
|
-
return "****"
|
299
|
-
return val[:2] + "*" * (len(val)-4) + val[-2:]
|
300
|
-
def redact_dict(d):
|
301
|
-
if isinstance(d, dict):
|
302
|
-
return {k: (redact_dict(v) if not (isinstance(v, str) and ("key" in k.lower() or "token" in k.lower() or "secret" in k.lower())) else redact(v)) for k, v in d.items()}
|
303
|
-
elif isinstance(d, list):
|
304
|
-
return [redact_dict(item) for item in d]
|
305
|
-
return d
|
306
|
-
try:
|
307
|
-
if self._config is None:
|
308
|
-
try:
|
309
|
-
# --- Get config from the AppConfig instance (Django) ---
|
310
|
-
app_config_instance = apps.get_app_config('swarm')
|
311
|
-
if not hasattr(app_config_instance, 'config') or not app_config_instance.config:
|
312
|
-
raise ValueError("AppConfig for 'swarm' does not have a valid 'config' attribute.")
|
313
|
-
self._config = app_config_instance.config
|
314
|
-
print("[SWARM_CONFIG_DEBUG] Loaded config from Django AppConfig.")
|
315
|
-
except Exception as e:
|
316
|
-
if _should_debug():
|
317
|
-
logger.warning(f"Falling back to CLI/home config due to error: {e}")
|
318
|
-
# 1. CLI argument (not handled here, handled in cli_handler)
|
319
|
-
# 2. Current working directory (guard against missing CWD)
|
320
|
-
try:
|
321
|
-
cwd_config = Path.cwd() / "swarm_config.json"
|
322
|
-
print(f"[SWARM_CONFIG_DEBUG] Trying: {cwd_config}")
|
323
|
-
except Exception as e:
|
324
|
-
cwd_config = None
|
325
|
-
if _should_debug():
|
326
|
-
logger.warning(f"Unable to determine CWD for config lookup: {e}")
|
327
|
-
if cwd_config and cwd_config.exists():
|
328
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {cwd_config}")
|
329
|
-
with open(cwd_config, 'r') as f:
|
330
|
-
self._config = json.load(f)
|
331
|
-
# 3. XDG_CONFIG_HOME or ~/.config/swarm/swarm_config.json
|
332
|
-
elif os.environ.get("XDG_CONFIG_HOME"):
|
333
|
-
xdg_config = Path(os.environ["XDG_CONFIG_HOME"]) / "swarm" / "swarm_config.json"
|
334
|
-
print(f"[SWARM_CONFIG_DEBUG] Trying: {xdg_config}")
|
335
|
-
if xdg_config.exists():
|
336
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {xdg_config}")
|
337
|
-
with open(xdg_config, 'r') as f:
|
338
|
-
self._config = json.load(f)
|
339
|
-
elif (Path.home() / ".config/swarm/swarm_config.json").exists():
|
340
|
-
home_config = Path.home() / ".config/swarm/swarm_config.json"
|
341
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {home_config}")
|
342
|
-
with open(home_config, 'r') as f:
|
343
|
-
self._config = json.load(f)
|
344
|
-
# 4. Legacy fallback: ~/.swarm/swarm_config.json
|
345
|
-
elif (Path.home() / ".swarm/swarm_config.json").exists():
|
346
|
-
legacy_config = Path.home() / ".swarm/swarm_config.json"
|
347
|
-
print(f"[SWARM_CONFIG_DEBUG] Loaded: {legacy_config}")
|
348
|
-
with open(legacy_config, 'r') as f:
|
349
|
-
self._config = json.load(f)
|
350
|
-
# 5. Fallback: OPENAI_API_KEY envvar
|
351
|
-
elif os.environ.get("OPENAI_API_KEY"):
|
352
|
-
print("[SWARM_CONFIG_DEBUG] No config file found, using OPENAI_API_KEY from env.")
|
353
|
-
self._config = {
|
354
|
-
"llm": {"default": {"provider": "openai", "model": "gpt-3.5-turbo", "api_key": os.environ["OPENAI_API_KEY"]}},
|
355
|
-
"settings": {"default_llm_profile": "default", "default_markdown_output": True},
|
356
|
-
"blueprints": {},
|
357
|
-
"llm_profile": "default",
|
358
|
-
"mcpServers": {}
|
359
|
-
}
|
360
|
-
logger.info("No config file found, using default config with OPENAI_API_KEY for CLI mode.")
|
361
|
-
else:
|
362
|
-
print("[SWARM_CONFIG_DEBUG] No config file found and OPENAI_API_KEY is not set. Using empty config.")
|
363
|
-
self._config = {}
|
364
|
-
logger.warning("No config file found and OPENAI_API_KEY is not set. Using empty config. CLI blueprints may fail if LLM config is required.")
|
365
|
-
if self._config is not None:
|
366
|
-
self._config = _substitute_env_vars(self._config)
|
367
|
-
# Ensure self._config is always a dict
|
368
|
-
if self._config is None:
|
369
|
-
self._config = {}
|
370
|
-
settings_section = self._config.get("settings", {})
|
371
|
-
llm_section = self._config.get("llm", {})
|
372
|
-
|
373
|
-
# --- After config is loaded, set OpenAI client from config if possible ---
|
374
|
-
try:
|
375
|
-
llm_profiles = self._config.get("llm", {})
|
376
|
-
default_profile = llm_profiles.get("default", {})
|
377
|
-
base_url = default_profile.get("base_url")
|
378
|
-
api_key = default_profile.get("api_key")
|
379
|
-
# Expand env vars if present
|
380
|
-
import os
|
381
|
-
if base_url and base_url.startswith("${"):
|
382
|
-
var = base_url[2:-1]
|
383
|
-
base_url = os.environ.get(var, base_url)
|
384
|
-
if api_key and api_key.startswith("${"):
|
385
|
-
var = api_key[2:-1]
|
386
|
-
api_key = os.environ.get(var, api_key)
|
387
|
-
if base_url and api_key:
|
388
|
-
from openai import AsyncOpenAI
|
389
|
-
from agents import set_default_openai_client
|
390
|
-
_debug_print(f"[DEBUG] (config) Setting OpenAI client: base_url={base_url}, api_key={'set' if api_key else 'NOT SET'}")
|
391
|
-
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
|
392
|
-
set_default_openai_client(client)
|
393
|
-
except Exception as e:
|
394
|
-
_debug_print(f"[DEBUG] Failed to set OpenAI client from config: {e}")
|
395
|
-
|
396
|
-
# --- Debug: Print and log redacted config ---
|
397
|
-
redacted_config = redact_dict(self._config)
|
398
|
-
logger.debug(f"Loaded config (redacted): {json.dumps(redacted_config, indent=2)}")
|
399
|
-
|
400
|
-
# --- Process LLM profile name and data ---
|
401
|
-
default_profile = settings_section.get("default_llm_profile") or "default"
|
402
|
-
# Only set self._llm_profile_name if explicitly provided in config
|
403
|
-
if "llm_profile" in self._config:
|
404
|
-
self._llm_profile_name = self._config["llm_profile"]
|
405
|
-
# Do NOT set self._llm_profile_name to default_profile here; let resolution logic handle fallback
|
406
|
-
if "profiles" in llm_section:
|
407
|
-
self._llm_profile_data = llm_section["profiles"].get(self._llm_profile_name, {})
|
408
|
-
else:
|
409
|
-
self._llm_profile_data = llm_section.get(self._llm_profile_name, {})
|
410
|
-
blueprint_specific_settings = self._config.get("blueprints", {}).get(self.blueprint_id, {})
|
411
|
-
global_markdown_setting = settings_section.get("default_markdown_output", True)
|
412
|
-
self._markdown_output = blueprint_specific_settings.get("markdown_output", global_markdown_setting)
|
413
|
-
logger.debug(f"Markdown output for '{self.blueprint_id}': {self._markdown_output}")
|
414
|
-
|
415
|
-
except ValueError as e:
|
416
|
-
logger.error(f"Configuration error for blueprint '{self.blueprint_id}': {e}", exc_info=True)
|
417
|
-
raise
|
418
|
-
except Exception as e:
|
419
|
-
logger.error(f"Unexpected error loading config for blueprint '{self.blueprint_id}': {e}", exc_info=True)
|
420
|
-
raise
|
421
|
-
|
422
|
-
def _resolve_llm_profile(self):
|
423
|
-
"""Resolve the LLM profile for this blueprint using the following order:
|
424
|
-
1. If self._llm_profile_name is set, use it.
|
425
|
-
2. If config has 'llm_profile', use it.
|
426
|
-
3. If config['blueprints'][blueprint_id or stripped]['llm_profile'] is set, use it.
|
427
|
-
4. If settings.default_llm in self._config, use it.
|
428
|
-
5. If global swarm_config has blueprints.<BlueprintName>.llm_profile, use it.
|
429
|
-
6. If settings.default_llm in global config, use it.
|
430
|
-
7. If env var DEFAULT_LLM is set, use it.
|
431
|
-
8. Otherwise, use 'default'.
|
432
|
-
"""
|
433
|
-
# Use cached value if already resolved
|
434
|
-
if getattr(self, '_resolved_llm_profile', None):
|
435
|
-
return self._resolved_llm_profile
|
436
|
-
name = getattr(self, 'blueprint_id', None) or getattr(self, '__class__', type(self)).__name__
|
437
|
-
profile = None
|
438
|
-
import logging
|
439
|
-
logger = logging.getLogger(__name__)
|
440
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] blueprint_id/name: {name}")
|
441
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] self._config: {self._config}")
|
442
|
-
# 1. Explicit override
|
443
|
-
if getattr(self, '_llm_profile_name', None):
|
444
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] Using programmatic override: {self._llm_profile_name}")
|
445
|
-
profile = self._llm_profile_name
|
446
|
-
# 2. Blueprint config (top-level)
|
447
|
-
elif self._config and self._config.get('llm_profile'):
|
448
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] Using top-level config llm_profile: {self._config['llm_profile']}")
|
449
|
-
profile = self._config['llm_profile']
|
450
|
-
# 3. Blueprint config (per-blueprint section)
|
451
|
-
elif self._config and self._config.get('blueprints'):
|
452
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] Checking per-blueprint config for: {name}")
|
453
|
-
bp_cfg = self._config['blueprints'].get(name) or self._config['blueprints'].get(name.replace('Blueprint',''))
|
454
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] bp_cfg: {bp_cfg}")
|
455
|
-
if isinstance(bp_cfg, dict) and 'llm_profile' in bp_cfg:
|
456
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] Using per-blueprint llm_profile: {bp_cfg['llm_profile']}")
|
457
|
-
profile = bp_cfg['llm_profile']
|
458
|
-
# 4. settings.default_llm in self._config
|
459
|
-
elif self._config and self._config.get('settings') and self._config['settings'].get('default_llm'):
|
460
|
-
profile = self._config['settings']['default_llm']
|
461
|
-
# 5. Global config lookup (blueprints.<BlueprintName>.llm_profile)
|
462
|
-
else:
|
463
|
-
global_config = None
|
464
|
-
try:
|
465
|
-
import json, os
|
466
|
-
from pathlib import Path
|
467
|
-
config_paths = [Path.cwd() / 'swarm_config.json', Path.home() / '.config/swarm/swarm_config.json']
|
468
|
-
for path in config_paths:
|
469
|
-
if path.exists():
|
470
|
-
with open(path) as f:
|
471
|
-
global_config = json.load(f)
|
472
|
-
break
|
473
|
-
except Exception:
|
474
|
-
global_config = None
|
475
|
-
if global_config and 'blueprints' in global_config:
|
476
|
-
bp_cfg = global_config['blueprints'].get(name) or global_config['blueprints'].get(name.replace('Blueprint',''))
|
477
|
-
if bp_cfg and 'llm_profile' in bp_cfg:
|
478
|
-
profile = bp_cfg['llm_profile']
|
479
|
-
# 6. settings.default_llm in global config
|
480
|
-
if not profile and global_config and 'settings' in global_config and global_config['settings'].get('default_llm'):
|
481
|
-
profile = global_config['settings']['default_llm']
|
482
|
-
# 7. Env var DEFAULT_LLM
|
483
|
-
if not profile:
|
484
|
-
import os
|
485
|
-
profile = os.environ.get('DEFAULT_LLM')
|
486
|
-
# 8. Otherwise, use 'default'
|
487
|
-
if not profile:
|
488
|
-
profile = 'default'
|
489
|
-
logger.debug(f"[DEBUG _resolve_llm_profile] Final resolved profile: {profile}")
|
490
|
-
self._resolved_llm_profile = profile
|
491
|
-
return profile
|
492
|
-
|
493
|
-
@property
|
494
|
-
def config(self) -> Dict[str, Any]:
|
495
|
-
"""Returns the loaded and processed Swarm configuration."""
|
496
|
-
if self._config is None:
|
497
|
-
raise RuntimeError("Configuration accessed before initialization or after failure.")
|
498
|
-
return self._config
|
499
|
-
|
500
|
-
@property
|
501
|
-
def llm_profile(self) -> Dict[str, Any]:
|
502
|
-
"""
|
503
|
-
Returns the LLM profile dict for this blueprint.
|
504
|
-
Raises a clear error if provider is missing.
|
505
|
-
"""
|
506
|
-
llm_section = self._config.get("llm", {}) if self._config else {}
|
507
|
-
profile_name = self._resolve_llm_profile()
|
508
|
-
profile = llm_section.get(profile_name)
|
509
|
-
if not profile:
|
510
|
-
raise ValueError(f"LLM profile '{profile_name}' not found in config: {llm_section}")
|
511
|
-
if "provider" not in profile:
|
512
|
-
raise ValueError(f"'provider' missing in LLM profile '{profile_name}': {profile}")
|
513
|
-
return profile
|
514
|
-
|
515
|
-
@property
|
516
|
-
def llm_profile_name(self) -> str:
|
517
|
-
"""Returns the name of the LLM profile being used."""
|
518
|
-
return self._resolve_llm_profile()
|
519
|
-
|
520
|
-
@llm_profile_name.setter
|
521
|
-
def llm_profile_name(self, value: str):
|
522
|
-
self._llm_profile_name = value
|
523
|
-
if hasattr(self, '_resolved_llm_profile'):
|
524
|
-
del self._resolved_llm_profile
|
525
|
-
|
526
|
-
@property
|
527
|
-
def slash_commands(self):
|
528
|
-
from swarm.core.slash_commands import slash_registry
|
529
|
-
return slash_registry
|
530
|
-
|
531
|
-
def get_llm_profile(self, profile_name: str) -> dict:
|
532
|
-
"""Returns the LLM profile dict for the given profile name from config, or empty dict if not found.
|
533
|
-
Supports both llm.profiles and direct llm keys for backward compatibility."""
|
534
|
-
llm_section = self.config.get("llm", {})
|
535
|
-
if "profiles" in llm_section:
|
536
|
-
return llm_section["profiles"].get(profile_name, {})
|
537
|
-
return llm_section.get(profile_name, {})
|
538
|
-
|
539
|
-
@property
|
540
|
-
def should_output_markdown(self) -> bool:
|
541
|
-
"""
|
542
|
-
Determines if markdown output should be used for this blueprint.
|
543
|
-
Priority: blueprint config > global config > False
|
544
|
-
"""
|
545
|
-
settings = self._config.get("settings", {}) if self._config else {}
|
546
|
-
bp_settings = self._config.get("blueprints", {}).get(self.blueprint_id, {}) if self._config else {}
|
547
|
-
if "output_markdown" in bp_settings:
|
548
|
-
return bool(bp_settings["output_markdown"])
|
549
|
-
if "default_markdown_output" in settings:
|
550
|
-
return bool(settings["default_markdown_output"])
|
551
|
-
return False
|
552
|
-
|
553
|
-
@property
|
554
|
-
def splash(self) -> str:
|
555
|
-
"""
|
556
|
-
Plain text splash/description for API, docs, etc.
|
557
|
-
"""
|
558
|
-
title = self.metadata.get('title', 'Blueprint')
|
559
|
-
desc = self.metadata.get('description', '')
|
560
|
-
return f"{title}: {desc}"
|
561
|
-
|
562
|
-
def get_cli_splash(self, color='cyan', emoji='🤖') -> str:
|
563
|
-
"""
|
564
|
-
CLI splash with ANSI/emoji, only for terminal output.
|
565
|
-
"""
|
566
|
-
from swarm.utils.ansi_box import ansi_box
|
567
|
-
return ansi_box(self.splash, color=color, emoji=emoji)
|
568
|
-
|
569
|
-
def _get_model_instance(self, profile_name: str):
|
570
|
-
"""Retrieves or creates an LLM Model instance, respecting LITELLM_MODEL/DEFAULT_LLM if set."""
|
571
|
-
if not hasattr(self, '_model_instance_cache'):
|
572
|
-
self._model_instance_cache = {}
|
573
|
-
if not hasattr(self, '_openai_client_cache'):
|
574
|
-
self._openai_client_cache = {}
|
575
|
-
if profile_name in self._model_instance_cache:
|
576
|
-
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
577
|
-
return self._model_instance_cache[profile_name]
|
578
|
-
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
579
|
-
profile_data = self.get_llm_profile(profile_name)
|
580
|
-
import os
|
581
|
-
# --- PATCH: API mode selection ---
|
582
|
-
# Default to 'completions' mode unless 'responses' is explicitly specified in swarm_config.json for this blueprint
|
583
|
-
api_mode = profile_data.get("api_mode") or self.config.get("api_mode") or "completions"
|
584
|
-
# Allow env override for debugging if needed
|
585
|
-
api_mode = os.getenv("SWARM_LLM_API_MODE", api_mode)
|
586
|
-
model_name = os.getenv("LITELLM_MODEL") or os.getenv("DEFAULT_LLM") or profile_data.get("model")
|
587
|
-
provider = profile_data.get("provider", "openai")
|
588
|
-
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
589
|
-
filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
590
|
-
log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
|
591
|
-
logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}' with {log_kwargs} and api_mode={api_mode}")
|
592
|
-
client_cache_key = f"{provider}_{profile_data.get('base_url')}_{api_mode}"
|
593
|
-
if client_cache_key not in self._openai_client_cache:
|
594
|
-
from openai import AsyncOpenAI
|
595
|
-
self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
|
596
|
-
client = self._openai_client_cache[client_cache_key]
|
597
|
-
# --- PATCH: Use correct model class based on api_mode ---
|
598
|
-
if api_mode == "responses":
|
599
|
-
from agents.models.openai_responses import OpenAIResponsesModel
|
600
|
-
model_instance = OpenAIResponsesModel(model=model_name, openai_client=client)
|
601
|
-
else:
|
602
|
-
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
603
|
-
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
|
604
|
-
self._model_instance_cache[profile_name] = model_instance
|
605
|
-
return model_instance
|
606
|
-
|
607
|
-
def make_agent(self, name, instructions, tools, mcp_servers=None, **kwargs):
|
608
|
-
"""Factory for creating an Agent with the correct model instance from framework config."""
|
609
|
-
from agents import Agent # Ensure Agent is always in scope
|
610
|
-
model_instance = self._get_model_instance(self._resolve_llm_profile())
|
611
|
-
return Agent(
|
612
|
-
name=name,
|
613
|
-
model=model_instance,
|
614
|
-
instructions=instructions,
|
615
|
-
tools=tools,
|
616
|
-
mcp_servers=mcp_servers or [],
|
617
|
-
**kwargs
|
618
|
-
)
|
619
|
-
|
620
|
-
def request_approval(self, action_type, action_summary, action_details=None):
|
621
|
-
"""
|
622
|
-
Prompt user for approval before executing an action.
|
623
|
-
Returns True if approved, False if rejected, or edited action if supported.
|
624
|
-
"""
|
625
|
-
try:
|
626
|
-
from swarm.core.blueprint_ux import BlueprintUX
|
627
|
-
ux = BlueprintUX(style="serious")
|
628
|
-
box = ux.box(f"Approve {action_type}?", action_summary, summary="Details:", params=action_details)
|
629
|
-
self.console.print(box)
|
630
|
-
except Exception:
|
631
|
-
print(f"Approve {action_type}?\n{action_summary}\nDetails: {action_details}")
|
632
|
-
while True:
|
633
|
-
resp = input("Approve this action? [y]es/[n]o/[e]dit/[s]kip: ").strip().lower()
|
634
|
-
if resp in ("y", "yes"): return True
|
635
|
-
if resp in ("n", "no"): return False
|
636
|
-
if resp in ("s", "skip"): return False
|
637
|
-
if resp in ("e", "edit"):
|
638
|
-
if action_details:
|
639
|
-
print("Edit not yet implemented; skipping.")
|
640
|
-
return False
|
641
|
-
else:
|
642
|
-
print("No editable content; skipping.")
|
643
|
-
return False
|
644
|
-
|
645
|
-
def execute_tool_with_approval(self, tool_func, action_type, action_summary, action_details=None, *args, **kwargs):
|
646
|
-
if getattr(self, 'approval_required', False):
|
647
|
-
approved = self.request_approval(action_type, action_summary, action_details)
|
648
|
-
if not approved:
|
649
|
-
try:
|
650
|
-
self.console.print(f"[yellow]Skipped {action_type}[/yellow]")
|
651
|
-
except Exception:
|
652
|
-
print(f"Skipped {action_type}")
|
653
|
-
return None
|
654
|
-
return tool_func(*args, **kwargs)
|
655
|
-
|
656
|
-
def start_session_logger(self, blueprint_name: str, global_instructions: str = None, project_instructions: str = None):
|
657
|
-
from swarm.core.session_logger import SessionLogger
|
658
|
-
self.session_logger = SessionLogger(blueprint_name=blueprint_name)
|
659
|
-
self.session_logger.log_instructions(global_instructions, project_instructions)
|
660
|
-
|
661
|
-
def log_message(self, role: str, content: str):
|
662
|
-
if self.session_logger:
|
663
|
-
self.session_logger.log_message(role, content)
|
664
|
-
|
665
|
-
def log_tool_call(self, tool_name: str, result: str):
|
666
|
-
if self.session_logger:
|
667
|
-
self.session_logger.log_tool_call(tool_name, result)
|
668
|
-
|
669
|
-
def close_session_logger(self):
|
670
|
-
if self.session_logger:
|
671
|
-
self.session_logger.close()
|
672
|
-
self.session_logger = None
|
673
|
-
|
674
|
-
def print_help(self):
|
675
|
-
"""
|
676
|
-
Print CLI usage/help for this blueprint. Subclasses can override for custom help.
|
677
|
-
"""
|
678
|
-
blueprint_name = getattr(self, 'blueprint_id', self.__class__.__name__)
|
679
|
-
print(f"\nUsage: {blueprint_name} [options] <prompt>\n")
|
680
|
-
print("Options:")
|
681
|
-
print(" -m, --model <model> Model to use for completions")
|
682
|
-
print(" -q, --quiet Non-interactive mode (only prints final output)")
|
683
|
-
print(" -o, --output <file> Output file")
|
684
|
-
print(" --project-doc <file> Include a markdown file as context")
|
685
|
-
print(" --full-context Load all project files as context")
|
686
|
-
print(" --approval <policy> Set approval policy for agent actions (suggest, auto-edit, full-auto)")
|
687
|
-
print(" --version Show version and exit")
|
688
|
-
print(" -h, --help Show this help message and exit\n")
|
689
|
-
print("Examples:")
|
690
|
-
print(f" {blueprint_name} \"Refactor all utils into a single module.\"")
|
691
|
-
print(f" {blueprint_name} --full-context \"Summarize all TODOs in the project.\"")
|
692
|
-
print(f" {blueprint_name} --approval full-auto \"Upgrade all dependencies and update the changelog.\"")
|
693
|
-
|
694
|
-
@abstractmethod
|
695
|
-
async def run(self, messages: List[Dict[str, Any]], **kwargs: Any) -> AsyncGenerator[Dict[str, Any], None]:
|
696
|
-
"""
|
697
|
-
The main execution method for the blueprint.
|
698
|
-
"""
|
699
|
-
import os
|
700
|
-
import pprint
|
701
|
-
logger.debug("ENVIRONMENT DUMP BEFORE MODEL CALL:")
|
702
|
-
pprint.pprint(dict(os.environ))
|
703
|
-
raise NotImplementedError("Subclasses must implement the 'run' method.")
|
704
|
-
yield {}
|
705
|
-
|
706
|
-
def _load_configuration(self):
|
707
|
-
"""
|
708
|
-
Loads blueprint configuration. This method is a stub for compatibility with tests that patch it.
|
709
|
-
In production, configuration is loaded via _load_and_process_config.
|
710
|
-
"""
|
711
|
-
import os
|
712
|
-
import json
|
713
|
-
from pathlib import Path
|
714
|
-
import traceback
|
715
|
-
try:
|
716
|
-
if self._config is None:
|
717
|
-
try:
|
718
|
-
if self.config_path is not None:
|
719
|
-
self.config_path = Path(self.config_path)
|
720
|
-
if self.config_path.exists():
|
721
|
-
if is_debug_enabled():
|
722
|
-
print(f"[DEBUG LOADER] Reading config from {self.config_path}")
|
723
|
-
raw = self.config_path.read_text()
|
724
|
-
if is_debug_enabled():
|
725
|
-
print(f"[DEBUG LOADER] Raw config contents:\n{raw}")
|
726
|
-
self._config = json.loads(raw)
|
727
|
-
assert isinstance(self._config, dict), f"Config not a dict: {type(self._config)}"
|
728
|
-
assert self._config, "Config loaded but is empty!"
|
729
|
-
else:
|
730
|
-
logger.warning(f"Config path {self.config_path} does not exist. Using empty config.")
|
731
|
-
self._config = {}
|
732
|
-
else:
|
733
|
-
# Try cwd, then default, then /mnt/models/open-swarm-mcp/swarm_config.json
|
734
|
-
cwd_path = Path(os.getcwd()) / "swarm_config.json"
|
735
|
-
if cwd_path.exists():
|
736
|
-
if is_debug_enabled():
|
737
|
-
print(f"[DEBUG LOADER] Reading config from {cwd_path}")
|
738
|
-
raw = cwd_path.read_text()
|
739
|
-
if is_debug_enabled():
|
740
|
-
print(f"[DEBUG LOADER] Raw config contents:\n{raw}")
|
741
|
-
self._config = json.loads(raw)
|
742
|
-
assert isinstance(self._config, dict), f"Config not a dict: {type(self._config)}"
|
743
|
-
assert self._config, "Config loaded but is empty!"
|
744
|
-
else:
|
745
|
-
# Fallback to /mnt/models/open-swarm-mcp/swarm_config.json
|
746
|
-
mnt_path = Path("/mnt/models/open-swarm-mcp/swarm_config.json")
|
747
|
-
if mnt_path.exists():
|
748
|
-
if is_debug_enabled():
|
749
|
-
print(f"[DEBUG LOADER] Reading config from {mnt_path}")
|
750
|
-
raw = mnt_path.read_text()
|
751
|
-
if is_debug_enabled():
|
752
|
-
print(f"[DEBUG LOADER] Raw config contents:\n{raw}")
|
753
|
-
self._config = json.loads(raw)
|
754
|
-
assert isinstance(self._config, dict), f"Config not a dict: {type(self._config)}"
|
755
|
-
assert self._config, "Config loaded but is empty!"
|
756
|
-
else:
|
757
|
-
self._config = {}
|
758
|
-
except Exception as e:
|
759
|
-
print(f"[FATAL CONFIG LOAD ERROR] {e}")
|
760
|
-
traceback.print_exc()
|
761
|
-
self._config = {}
|
762
|
-
# Ensure self._config is always a dict
|
763
|
-
if self._config is None:
|
764
|
-
self._config = {}
|
765
|
-
return self._config
|
766
|
-
|
767
|
-
except Exception as e:
|
768
|
-
logger.error(f"Unexpected error loading config for blueprint '{self.blueprint_id}': {e}", exc_info=True)
|
769
|
-
raise
|