open-swarm 0.1.1744936173__py3-none-any.whl → 0.1.1744936297__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.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/METADATA +1 -1
- {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/RECORD +27 -27
- {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/entry_points.txt +1 -0
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +28 -0
- swarm/blueprints/divine_code/blueprint_divine_code.py +26 -0
- swarm/blueprints/django_chat/blueprint_django_chat.py +15 -4
- swarm/blueprints/echocraft/blueprint_echocraft.py +9 -2
- swarm/blueprints/family_ties/blueprint_family_ties.py +28 -0
- swarm/blueprints/gaggle/blueprint_gaggle.py +117 -15
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +10 -0
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +47 -29
- swarm/blueprints/omniplex/blueprint_omniplex.py +21 -0
- swarm/blueprints/rue_code/blueprint_rue_code.py +24 -25
- swarm/blueprints/suggestion/blueprint_suggestion.py +35 -12
- swarm/consumers.py +19 -0
- swarm/extensions/blueprint/agent_utils.py +1 -1
- swarm/extensions/blueprint/blueprint_base.py +265 -43
- swarm/extensions/blueprint/blueprint_discovery.py +13 -11
- swarm/extensions/blueprint/cli_handler.py +33 -55
- swarm/extensions/blueprint/output_utils.py +78 -0
- swarm/extensions/blueprint/spinner.py +30 -21
- swarm/extensions/cli/cli_args.py +6 -0
- swarm/extensions/config/config_loader.py +4 -1
- swarm/llm/chat_completion.py +31 -1
- swarm/settings.py +6 -7
- {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/licenses/LICENSE +0 -0
@@ -6,29 +6,36 @@ import os
|
|
6
6
|
import sys
|
7
7
|
import threading
|
8
8
|
import time
|
9
|
+
from typing import Optional
|
9
10
|
|
10
11
|
class Spinner:
|
11
12
|
"""Simple terminal spinner for interactive feedback."""
|
12
13
|
# Define spinner characters (can be customized)
|
13
14
|
SPINNER_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
14
|
-
#
|
15
|
+
# Custom status sequences for special cases
|
16
|
+
STATUS_SEQUENCES = {
|
17
|
+
'generating': ['Generating.', 'Generating..', 'Generating...'],
|
18
|
+
'running': ['Running...']
|
19
|
+
}
|
15
20
|
|
16
|
-
def __init__(self, interactive: bool):
|
21
|
+
def __init__(self, interactive: bool, custom_sequence: str = None):
|
17
22
|
"""
|
18
23
|
Initialize the spinner.
|
19
24
|
|
20
25
|
Args:
|
21
26
|
interactive (bool): Hint whether the environment is interactive.
|
22
27
|
Spinner is disabled if False or if output is not a TTY.
|
28
|
+
custom_sequence (str): Optional name for a custom status sequence (e.g., 'generating', 'running').
|
23
29
|
"""
|
24
30
|
self.interactive = interactive
|
25
|
-
# Check if output is a TTY (terminal) and interactive flag is True
|
26
31
|
self.is_tty = sys.stdout.isatty()
|
27
32
|
self.enabled = self.interactive and self.is_tty
|
28
33
|
self.running = False
|
29
34
|
self.thread: Optional[threading.Thread] = None
|
30
35
|
self.status = ""
|
31
36
|
self.index = 0
|
37
|
+
self.custom_sequence = custom_sequence
|
38
|
+
self.sequence_idx = 0
|
32
39
|
|
33
40
|
def start(self, status: str = "Processing..."):
|
34
41
|
"""Start the spinner with an optional status message."""
|
@@ -36,7 +43,7 @@ class Spinner:
|
|
36
43
|
return # Do nothing if disabled or already running
|
37
44
|
self.status = status
|
38
45
|
self.running = True
|
39
|
-
|
46
|
+
self.sequence_idx = 0
|
40
47
|
self.thread = threading.Thread(target=self._spin, daemon=True)
|
41
48
|
self.thread.start()
|
42
49
|
|
@@ -47,30 +54,33 @@ class Spinner:
|
|
47
54
|
self.running = False
|
48
55
|
if self.thread is not None:
|
49
56
|
self.thread.join() # Wait for the thread to finish
|
50
|
-
# Clear the spinner line using ANSI escape codes
|
51
|
-
# \r: Carriage return (move cursor to beginning of line)
|
52
|
-
# \033[K: Clear line from cursor to end
|
53
57
|
sys.stdout.write("\r\033[K")
|
54
58
|
sys.stdout.flush()
|
55
|
-
self.thread = None
|
59
|
+
self.thread = None
|
56
60
|
|
57
61
|
def _spin(self):
|
58
62
|
"""Internal method running in the spinner thread to animate."""
|
63
|
+
start_time = time.time()
|
64
|
+
warned = False
|
59
65
|
while self.running:
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
66
|
+
elapsed = time.time() - start_time
|
67
|
+
if self.custom_sequence and self.custom_sequence in self.STATUS_SEQUENCES:
|
68
|
+
seq = self.STATUS_SEQUENCES[self.custom_sequence]
|
69
|
+
# If taking longer than 10s, show special message
|
70
|
+
if elapsed > 10 and not warned:
|
71
|
+
msg = f"{seq[-1]} Taking longer than expected"
|
72
|
+
warned = True
|
73
|
+
else:
|
74
|
+
msg = seq[self.sequence_idx % len(seq)]
|
75
|
+
sys.stdout.write(f"\r{msg}\033[K")
|
76
|
+
sys.stdout.flush()
|
77
|
+
self.sequence_idx += 1
|
78
|
+
else:
|
79
|
+
char = self.SPINNER_CHARS[self.index % len(self.SPINNER_CHARS)]
|
65
80
|
sys.stdout.write(f"\r{char} {self.status}\033[K")
|
66
81
|
sys.stdout.flush()
|
67
|
-
|
68
|
-
|
69
|
-
time.sleep(0.1)
|
70
|
-
continue
|
71
|
-
self.index += 1
|
72
|
-
# Pause for animation effect
|
73
|
-
time.sleep(0.1)
|
82
|
+
self.index += 1
|
83
|
+
time.sleep(0.4 if self.custom_sequence else 0.1)
|
74
84
|
|
75
85
|
# Example usage (if run directly)
|
76
86
|
if __name__ == "__main__":
|
@@ -88,4 +98,3 @@ if __name__ == "__main__":
|
|
88
98
|
finally:
|
89
99
|
s.stop() # Ensure spinner stops on exit/error
|
90
100
|
print("Test finished.")
|
91
|
-
|
swarm/extensions/cli/cli_args.py
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# src/swarm/extensions/blueprint/modes/cli_mode/cli_args.py
|
2
2
|
|
3
3
|
import argparse
|
4
|
+
import os
|
5
|
+
import sys
|
4
6
|
from typing import Namespace
|
5
7
|
|
8
|
+
# --- DEBUG PRINTS REMOVED BY CASCADE ---
|
9
|
+
# print(f"[DEBUG] cli_args.py startup: sys.argv={sys.argv}")
|
10
|
+
# print(f"[DEBUG] cli_args.py startup: LITELLM_MODEL={os.environ.get('LITELLM_MODEL')}, DEFAULT_LLM={os.environ.get('DEFAULT_LLM')}")
|
11
|
+
|
6
12
|
def parse_arguments() -> Namespace:
|
7
13
|
"""
|
8
14
|
Parse command-line arguments for dynamic LLM configuration, MCP server management, and other overrides.
|
@@ -57,6 +57,10 @@ def _substitute_env_vars_recursive(data: Any) -> Any:
|
|
57
57
|
if isinstance(data,str): return os.path.expandvars(data)
|
58
58
|
return data
|
59
59
|
|
60
|
+
def _substitute_env_vars(data: Any) -> Any:
|
61
|
+
"""Public API: Recursively substitute environment variables in dict, list, str."""
|
62
|
+
return _substitute_env_vars_recursive(data)
|
63
|
+
|
60
64
|
def create_default_config(config_path: Path):
|
61
65
|
"""Creates a default configuration file with valid JSON."""
|
62
66
|
default_config = {
|
@@ -88,4 +92,3 @@ def create_default_config(config_path: Path):
|
|
88
92
|
except Exception as e:
|
89
93
|
logger.error(f"Failed to create default config file at {config_path}: {e}", exc_info=True)
|
90
94
|
raise
|
91
|
-
|
swarm/llm/chat_completion.py
CHANGED
@@ -18,7 +18,6 @@ from ..utils.redact import redact_sensitive_data
|
|
18
18
|
from ..utils.general_utils import serialize_datetime
|
19
19
|
from ..utils.message_utils import filter_duplicate_system_messages, update_null_content
|
20
20
|
from ..utils.context_utils import get_token_count, truncate_message_history
|
21
|
-
# --- REMOVED import: from ..utils.message_sequence import repair_message_payload ---
|
22
21
|
|
23
22
|
# Configure module-level logging
|
24
23
|
logger = logging.getLogger(__name__)
|
@@ -29,6 +28,29 @@ if not logger.handlers:
|
|
29
28
|
stream_handler.setFormatter(formatter)
|
30
29
|
logger.addHandler(stream_handler)
|
31
30
|
|
31
|
+
# --- PATCH: Suppress OpenAI tracing/telemetry errors if using LiteLLM/custom endpoint ---
|
32
|
+
import logging
|
33
|
+
import os
|
34
|
+
if os.environ.get("LITELLM_BASE_URL") or os.environ.get("OPENAI_BASE_URL"):
|
35
|
+
# Silence openai.agents tracing/telemetry errors
|
36
|
+
logging.getLogger("openai.agents").setLevel(logging.CRITICAL)
|
37
|
+
try:
|
38
|
+
import openai.agents.tracing
|
39
|
+
openai.agents.tracing.TracingClient = lambda *a, **kw: None
|
40
|
+
except Exception:
|
41
|
+
pass
|
42
|
+
|
43
|
+
# --- PATCH: Enforce custom endpoint, never fallback to OpenAI if custom base_url is set ---
|
44
|
+
def _enforce_litellm_only(client):
|
45
|
+
# If client has a base_url attribute, check it
|
46
|
+
base_url = getattr(client, 'base_url', None)
|
47
|
+
if base_url and 'openai.com' in base_url:
|
48
|
+
return # Using OpenAI, allowed
|
49
|
+
if base_url and 'openai.com' not in base_url:
|
50
|
+
# If any fallback to OpenAI API is attempted, raise error
|
51
|
+
import traceback
|
52
|
+
raise RuntimeError(f"Attempted fallback to OpenAI API when custom base_url is set! base_url={base_url}\n{traceback.format_stack()}")
|
53
|
+
|
32
54
|
|
33
55
|
async def get_chat_completion(
|
34
56
|
client: AsyncOpenAI,
|
@@ -44,6 +66,7 @@ async def get_chat_completion(
|
|
44
66
|
stream: bool = False,
|
45
67
|
debug: bool = False
|
46
68
|
) -> Union[Dict[str, Any], AsyncGenerator[Any, None]]:
|
69
|
+
_enforce_litellm_only(client)
|
47
70
|
"""
|
48
71
|
Retrieve a chat completion from the LLM for the given agent and history.
|
49
72
|
Relies on openai-agents Runner for actual execution, this might become deprecated.
|
@@ -62,6 +85,12 @@ async def get_chat_completion(
|
|
62
85
|
redacted_kwargs = redact_sensitive_data(client_kwargs, sensitive_keys=["api_key"])
|
63
86
|
logger.debug(f"Using client with model='{active_model}', base_url='{client_kwargs.get('base_url', 'default')}', api_key={redacted_kwargs['api_key']}")
|
64
87
|
|
88
|
+
# --- ENFORCE: Disallow fallback to OpenAI if custom base_url is set ---
|
89
|
+
if client_kwargs.get("base_url") and "openai.com" not in client_kwargs["base_url"]:
|
90
|
+
# If the base_url is set and is not OpenAI, ensure no fallback to OpenAI API
|
91
|
+
if "openai.com" in os.environ.get("OPENAI_API_BASE", ""):
|
92
|
+
raise RuntimeError(f"[SECURITY] Fallback to OpenAI API attempted with base_url={client_kwargs['base_url']}. Refusing for safety.")
|
93
|
+
|
65
94
|
context_variables = defaultdict(str, context_variables)
|
66
95
|
instructions = agent.instructions(context_variables) if callable(agent.instructions) else agent.instructions
|
67
96
|
if not isinstance(instructions, str):
|
@@ -152,6 +181,7 @@ async def get_chat_completion_message(
|
|
152
181
|
stream: bool = False,
|
153
182
|
debug: bool = False
|
154
183
|
) -> Union[Dict[str, Any], AsyncGenerator[Any, None]]:
|
184
|
+
_enforce_litellm_only(client)
|
155
185
|
"""
|
156
186
|
Wrapper to retrieve and validate a chat completion message (returns dict or stream).
|
157
187
|
Relies on openai-agents Runner for actual execution, this might become deprecated.
|
swarm/settings.py
CHANGED
@@ -12,7 +12,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # Points to src/
|
|
12
12
|
# --- Load .env file ---
|
13
13
|
dotenv_path = BASE_DIR.parent / '.env'
|
14
14
|
load_dotenv(dotenv_path=dotenv_path)
|
15
|
-
print(f"[Settings] Attempted to load .env from: {dotenv_path}")
|
15
|
+
# print(f"[Settings] Attempted to load .env from: {dotenv_path}")
|
16
16
|
# ---
|
17
17
|
|
18
18
|
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'django-insecure-fallback-key-for-dev')
|
@@ -30,12 +30,12 @@ SWARM_API_KEY = _raw_api_token # Assign the loaded token (or None)
|
|
30
30
|
if ENABLE_API_AUTH:
|
31
31
|
# Add assertion to satisfy type checkers within this block
|
32
32
|
assert SWARM_API_KEY is not None, "SWARM_API_KEY cannot be None when ENABLE_API_AUTH is True"
|
33
|
-
print(f"[Settings] SWARM_API_KEY loaded: {SWARM_API_KEY[:4]}...{SWARM_API_KEY[-4:]}")
|
34
|
-
print("[Settings] ENABLE_API_AUTH is True.")
|
33
|
+
# print(f"[Settings] SWARM_API_KEY loaded: {SWARM_API_KEY[:4]}...{SWARM_API_KEY[-4:]}")
|
34
|
+
# print("[Settings] ENABLE_API_AUTH is True.")
|
35
35
|
else:
|
36
|
-
print("[Settings] API_AUTH_TOKEN env var not set. SWARM_API_KEY is None.")
|
37
|
-
print("[Settings] ENABLE_API_AUTH is False.")
|
38
|
-
|
36
|
+
# print("[Settings] API_AUTH_TOKEN env var not set. SWARM_API_KEY is None.")
|
37
|
+
# print("[Settings] ENABLE_API_AUTH is False.")
|
38
|
+
pass
|
39
39
|
|
40
40
|
SWARM_CONFIG_PATH = os.getenv('SWARM_CONFIG_PATH', str(BASE_DIR.parent / 'swarm_config.json'))
|
41
41
|
BLUEPRINT_DIRECTORY = os.getenv('BLUEPRINT_DIRECTORY', str(BASE_DIR / 'swarm' / 'blueprints'))
|
@@ -175,4 +175,3 @@ LOGIN_URL = '/login/'
|
|
175
175
|
LOGIN_REDIRECT_URL = '/'
|
176
176
|
LOGOUT_REDIRECT_URL = '/'
|
177
177
|
CSRF_TRUSTED_ORIGINS = os.getenv('DJANGO_CSRF_TRUSTED_ORIGINS', 'http://localhost:8000,http://127.0.0.1:8000').split(',')
|
178
|
-
|
File without changes
|
{open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/licenses/LICENSE
RENAMED
File without changes
|