open-swarm 0.1.1744942800__py3-none-any.whl → 0.1.1744942884__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.1744942800.dist-info → open_swarm-0.1.1744942884.dist-info}/METADATA +1 -1
- {open_swarm-0.1.1744942800.dist-info → open_swarm-0.1.1744942884.dist-info}/RECORD +9 -28
- swarm/extensions/blueprint/__init__.py +1 -2
- swarm/views/core_views.py +4 -6
- swarm/views/utils.py +1 -1
- swarm/views/web_views.py +2 -2
- swarm/extensions/blueprint/agent_utils.py +0 -21
- swarm/extensions/blueprint/blueprint_base.py +0 -333
- swarm/extensions/blueprint/blueprint_discovery.py +0 -128
- swarm/extensions/blueprint/blueprint_utils.py +0 -17
- swarm/extensions/blueprint/common_utils.py +0 -12
- swarm/extensions/blueprint/config_loader.py +0 -122
- swarm/extensions/blueprint/output_utils.py +0 -173
- swarm/extensions/blueprint/slash_commands.py +0 -17
- swarm/extensions/blueprint/spinner.py +0 -100
- swarm/extensions/config/config_manager.py +0 -258
- swarm/extensions/config/server_config.py +0 -49
- swarm/extensions/config/setup_wizard.py +0 -103
- swarm/extensions/config/utils/__init__.py +0 -0
- swarm/extensions/config/utils/logger.py +0 -36
- swarm/extensions/launchers/build_launchers.py +0 -14
- swarm/extensions/launchers/build_swarm_wrapper.py +0 -12
- swarm/extensions/launchers/swarm_api.py +0 -68
- swarm/extensions/launchers/swarm_cli.py +0 -216
- swarm/extensions/launchers/swarm_wrapper.py +0 -29
- {open_swarm-0.1.1744942800.dist-info → open_swarm-0.1.1744942884.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1744942800.dist-info → open_swarm-0.1.1744942884.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1744942800.dist-info → open_swarm-0.1.1744942884.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: open-swarm
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.1744942884
|
4
4
|
Summary: Open Swarm: Orchestrating AI Agent Swarms with Django
|
5
5
|
Project-URL: Homepage, https://github.com/yourusername/open-swarm
|
6
6
|
Project-URL: Documentation, https://github.com/yourusername/open-swarm/blob/main/README.md
|
@@ -50,20 +50,11 @@ swarm/blueprints/whiskeytango_foxtrot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
50
50
|
swarm/blueprints/whiskeytango_foxtrot/apps.py,sha256=V1QKvyb2Vz-EtDNhhNe4tw2W9LYhNDuiaIq_fAU4ilw,334
|
51
51
|
swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py,sha256=8PjKGDHSTaQ76DXPD3T4MXQ8uLIlm4xmJ5s0i64a_Jw,16179
|
52
52
|
swarm/extensions/__init__.py,sha256=SadbzfxckByaaqzuKPfXMvqmj45-dcMlavlfQYhGnzE,56
|
53
|
-
swarm/extensions/blueprint/__init__.py,sha256=
|
54
|
-
swarm/extensions/blueprint/agent_utils.py,sha256=exKnbJEm1VRL270x6XqQXHtJhqD8ogY3ZBIGZO_tYUE,552
|
55
|
-
swarm/extensions/blueprint/blueprint_base.py,sha256=GI1vFcrU2oZpDpqnzWEZNe9O0jJpTfOXuAvmBfWYOcg,15435
|
56
|
-
swarm/extensions/blueprint/blueprint_discovery.py,sha256=v9lJeFDvPI919NzFjaCvmFBix5n0ceeL9y2JWGr_uLw,5720
|
57
|
-
swarm/extensions/blueprint/blueprint_utils.py,sha256=Ef_pu-RYomqzFjMg6LOSPSdbYFCbYXjEoSvK1OT49Eo,702
|
53
|
+
swarm/extensions/blueprint/__init__.py,sha256=CqPy-gR0KAeVGxDnOWKOYZZzVC1FXRzQDtUCY8jmP1c,1850
|
58
54
|
swarm/extensions/blueprint/cli_handler.py,sha256=ZdjnTmBzSimQv8rCTwI3ZDp_2Zrjf9hqWLXYD3jzMck,9091
|
59
|
-
swarm/extensions/blueprint/common_utils.py,sha256=jeKcN3lMdrpOYWIpErH3L5am13jHjaImpVvk2b0mps4,462
|
60
|
-
swarm/extensions/blueprint/config_loader.py,sha256=ldQGtv4tXeDJzL2GCylDxykZxYBo4ALFY2kS0jZ79Eo,5652
|
61
55
|
swarm/extensions/blueprint/django_utils.py,sha256=ObtkmF1JW4H2OEYa7vC6ussUsMBtDsZTTVeHGHI-GOQ,17457
|
62
56
|
swarm/extensions/blueprint/interactive_mode.py,sha256=vGmMuAgC93TLjMi2RkXQ2FkWfIUblyOTFGHmVdGKLSQ,4572
|
63
|
-
swarm/extensions/blueprint/output_utils.py,sha256=HGpXIujoJNM5nCCzXH0Upog_ctw5BuftmMBiPujh-ZM,7139
|
64
57
|
swarm/extensions/blueprint/runnable_blueprint.py,sha256=1MywZ54vUysLVtYmwCbcDYQmQnoZffCHgsArbe-VKe8,1813
|
65
|
-
swarm/extensions/blueprint/slash_commands.py,sha256=5LEO_veo50_eRDmiGPNnFsI-I6-X-C9NvNNmu1187T0,498
|
66
|
-
swarm/extensions/blueprint/spinner.py,sha256=9lyjzLnQBdEBy_dXr6N6I7nxx6KfrNp7wf44sQN06GU,3756
|
67
58
|
swarm/extensions/blueprint/modes/rest_mode.py,sha256=KZuB_j2NfomER7CmlsLBqRipU3DymKY-9RpoGilMH0I,1357
|
68
59
|
swarm/extensions/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
69
60
|
swarm/extensions/cli/blueprint_runner.py,sha256=CG6XfOiDWuc84I_qefBpkwxEs7JcIbvNJqce9jBYUxo,9158
|
@@ -83,17 +74,7 @@ swarm/extensions/cli/utils/discover_commands.py,sha256=aJdU3kSmLlpBxzGdfOA88AaCw
|
|
83
74
|
swarm/extensions/cli/utils/env_setup.py,sha256=k7QxRjzIGx5HC6RVZP9QSaaXEKMkcKCewD66u0e7qfE,496
|
84
75
|
swarm/extensions/config/__init__.py,sha256=WjmGxMU5k3S40TNQxTfByYcT2YAchq_5gzXFWDLrLzU,141
|
85
76
|
swarm/extensions/config/config_loader.py,sha256=q-zO8qnKudCfoVOKYak5RXbvozMYTygLtVgU4BGYPi4,4992
|
86
|
-
swarm/extensions/config/config_manager.py,sha256=bBT-NGbRdsXoJ-lxZM5kjAf5FvVVGRVdbbCQJiCX1_0,9951
|
87
|
-
swarm/extensions/config/server_config.py,sha256=iBlQOaFQmkEPNLmVpAQvXlSWhTsu-YMIjEf-H-7QfUU,1882
|
88
|
-
swarm/extensions/config/setup_wizard.py,sha256=yAZ7MOgc8ZGti2kjZ72G6QLFBI0lbhXAa7Wi7SeXDYo,4567
|
89
|
-
swarm/extensions/config/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
90
|
-
swarm/extensions/config/utils/logger.py,sha256=sUkKdZsjRiCGGCEPfZ4YqeZDbiaUeGFOa3mdbVOTmvU,1242
|
91
77
|
swarm/extensions/launchers/__init__.py,sha256=RR4up00HKqXOOToxb63mKZ2o9fq-o75bUw7dZuxkAVk,56
|
92
|
-
swarm/extensions/launchers/build_launchers.py,sha256=Eh3ODR9L4KinG1GDK4-lF3NE0EZ-ArCrP3fxtGYhXgs,433
|
93
|
-
swarm/extensions/launchers/build_swarm_wrapper.py,sha256=c_9oR3To4M2cZyc1uYSiysHLhUGX5FkCAQk9AG7Va2Q,231
|
94
|
-
swarm/extensions/launchers/swarm_api.py,sha256=f8olTI5JVdayp923etVQWsP8WRquPG5Mw3Q40ItN6kY,2877
|
95
|
-
swarm/extensions/launchers/swarm_cli.py,sha256=dlvMq2HvUI2XlADuTzM8kpeedPkqzKB6k0oy7z2V_p0,9747
|
96
|
-
swarm/extensions/launchers/swarm_wrapper.py,sha256=3K58yqPN4Ct0c7zfSDKRIGdL1Q7WOBXmAVHXT-aaPj4,1004
|
97
78
|
swarm/llm/chat_completion.py,sha256=KR5ibJFZPBDKNuNIdlpushw6bcOVoY-jDeP-NJX4Qeg,9134
|
98
79
|
swarm/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
99
80
|
swarm/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -249,13 +230,13 @@ swarm/utils/redact.py,sha256=L2lo927S575Ay7Jz6pUlK47Sxxzpd9IMybnDm6-WlC8,2955
|
|
249
230
|
swarm/views/__init__.py,sha256=AcLk0R7Y69FhIVgJK2hZs8M_gCR-h_5iqUywz89yuHM,1223
|
250
231
|
swarm/views/api_views.py,sha256=BbDEgI6Ftg-c-mMkE9DvRGZHIZ-WAZSfwqAB7j98WxM,1937
|
251
232
|
swarm/views/chat_views.py,sha256=6UUtEJKrM2k_wi9A6AfhbbvMYunjzpY22M6hOIXASjA,15695
|
252
|
-
swarm/views/core_views.py,sha256=
|
233
|
+
swarm/views/core_views.py,sha256=UJ3FlrQSyLVLA791lDPRfiVmidpVN2aOeMt0nFZfVjU,5256
|
253
234
|
swarm/views/message_views.py,sha256=sDUnXyqKXC8WwIIMAlWf00s2_a2T9c75Na5FvYMJwBM,1596
|
254
235
|
swarm/views/model_views.py,sha256=aAbU4AZmrOTaPeKMWtoKK7FPYHdaN3Zbx55JfKzYTRY,2937
|
255
|
-
swarm/views/utils.py,sha256=
|
256
|
-
swarm/views/web_views.py,sha256=
|
257
|
-
open_swarm-0.1.
|
258
|
-
open_swarm-0.1.
|
259
|
-
open_swarm-0.1.
|
260
|
-
open_swarm-0.1.
|
261
|
-
open_swarm-0.1.
|
236
|
+
swarm/views/utils.py,sha256=8Usc0g0L0NPegNAyY20tJBNBy-JLwODf4VmxV0yUtpw,3627
|
237
|
+
swarm/views/web_views.py,sha256=T1CKe-Nyv1C8aDt6QFTGWo_dkH7ojWAvS_QW9mZnZp0,7371
|
238
|
+
open_swarm-0.1.1744942884.dist-info/METADATA,sha256=llG-oRb-Jxg-dS1HS00UaIegqr-pBH3_xlkmuy_ubvk,18813
|
239
|
+
open_swarm-0.1.1744942884.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
240
|
+
open_swarm-0.1.1744942884.dist-info/entry_points.txt,sha256=fo28d0_zJrytRsh8QqkdlWQT_9lyAwYUx1WuSTDI3HM,177
|
241
|
+
open_swarm-0.1.1744942884.dist-info/licenses/LICENSE,sha256=BU9bwRlnOt_JDIb6OT55Q4leLZx9RArDLTFnlDIrBEI,1062
|
242
|
+
open_swarm-0.1.1744942884.dist-info/RECORD,,
|
@@ -6,7 +6,7 @@ and running autonomous agent workflows (blueprints).
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
# Core components
|
9
|
-
from .blueprint_base import BlueprintBase
|
9
|
+
from swarm.core.blueprint_base import BlueprintBase
|
10
10
|
from .blueprint_discovery import discover_blueprints
|
11
11
|
from .blueprint_utils import filter_blueprints
|
12
12
|
|
@@ -48,4 +48,3 @@ __all__ = [
|
|
48
48
|
"validate_message_sequence",
|
49
49
|
"truncate_message_history",
|
50
50
|
]
|
51
|
-
|
swarm/views/core_views.py
CHANGED
@@ -18,7 +18,7 @@ from django.contrib.auth.forms import AuthenticationForm # Use standard auth for
|
|
18
18
|
# from .utils import blueprints_metadata # Or however metadata is accessed
|
19
19
|
|
20
20
|
# Use the current config loader
|
21
|
-
from swarm.
|
21
|
+
from swarm.core import config_loader, server_config
|
22
22
|
|
23
23
|
logger = logging.getLogger(__name__)
|
24
24
|
|
@@ -70,16 +70,16 @@ def serve_swarm_config(request):
|
|
70
70
|
config_path = None
|
71
71
|
try:
|
72
72
|
# Use the same logic as BlueprintBase if possible, or find_config_file
|
73
|
-
config_path = find_config_file(filename=DEFAULT_CONFIG_FILENAME, start_dir=Path(settings.BASE_DIR).parent) # Search from project root
|
73
|
+
config_path = config_loader.find_config_file(filename=config_loader.DEFAULT_CONFIG_FILENAME, start_dir=Path(settings.BASE_DIR).parent) # Search from project root
|
74
74
|
if not config_path:
|
75
75
|
# Fallback to location relative to settings? Unlikely to be correct.
|
76
|
-
config_path = Path(settings.BASE_DIR) / '..' / DEFAULT_CONFIG_FILENAME # Adjust relative path if needed
|
76
|
+
config_path = Path(settings.BASE_DIR) / '..' / config_loader.DEFAULT_CONFIG_FILENAME # Adjust relative path if needed
|
77
77
|
config_path = config_path.resolve()
|
78
78
|
|
79
79
|
if config_path and config_path.exists():
|
80
80
|
logger.info(f"Serving config from: {config_path}")
|
81
81
|
# Load config to potentially redact sensitive info before serving
|
82
|
-
config_data = load_config(config_path)
|
82
|
+
config_data = config_loader.load_config(config_path)
|
83
83
|
# Redact sensitive keys (e.g., api_key)
|
84
84
|
if 'llm' in config_data:
|
85
85
|
for profile in config_data['llm']: config_data['llm'][profile].pop('api_key', None)
|
@@ -109,5 +109,3 @@ def list_available_blueprints_api(request):
|
|
109
109
|
except Exception as e:
|
110
110
|
logger.error(f"Error listing blueprints via API: {e}", exc_info=True)
|
111
111
|
return JsonResponse({"error": "Failed to list blueprints."}, status=500)
|
112
|
-
|
113
|
-
|
swarm/views/utils.py
CHANGED
@@ -3,7 +3,7 @@ from django.conf import settings
|
|
3
3
|
from asgiref.sync import sync_to_async, async_to_sync
|
4
4
|
|
5
5
|
# Assuming the discovery functions are correctly located now
|
6
|
-
from swarm.
|
6
|
+
from swarm.core.blueprint_discovery import discover_blueprints
|
7
7
|
|
8
8
|
logger = logging.getLogger(__name__)
|
9
9
|
|
swarm/views/web_views.py
CHANGED
@@ -14,11 +14,11 @@ from django.contrib.auth.models import User
|
|
14
14
|
|
15
15
|
from swarm.utils.logger_setup import setup_logger
|
16
16
|
# Import the function to discover blueprints dynamically
|
17
|
-
from swarm.
|
17
|
+
from swarm.core.blueprint_discovery import discover_blueprints
|
18
18
|
# Import the setting for the blueprints directory
|
19
19
|
from swarm.settings import BLUEPRINTS_DIR
|
20
20
|
# Import config loader if needed, or assume config is loaded elsewhere
|
21
|
-
from swarm.
|
21
|
+
from swarm.core import config_loader, server_config
|
22
22
|
|
23
23
|
logger = setup_logger(__name__)
|
24
24
|
|
@@ -1,21 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
agent_utils.py
|
3
|
-
|
4
|
-
Utility functions for agent operations used in blueprints.
|
5
|
-
This module has been updated to remove dependency on swarm.types;
|
6
|
-
instead, it now imports Agent from the openai-agents SDK.
|
7
|
-
"""
|
8
|
-
|
9
|
-
from blueprint_agents.agent import Agent # Updated import
|
10
|
-
|
11
|
-
def get_agent_name(agent: Agent) -> str:
|
12
|
-
"""
|
13
|
-
Returns the name of the agent.
|
14
|
-
"""
|
15
|
-
return agent.name
|
16
|
-
|
17
|
-
def initialize_agents(blueprint) -> dict:
|
18
|
-
"""
|
19
|
-
Initializes agents by calling the blueprint's create_agents() method.
|
20
|
-
"""
|
21
|
-
return blueprint.create_agents()
|
@@ -1,333 +0,0 @@
|
|
1
|
-
# --- REMOVE noisy debug/framework prints unless SWARM_DEBUG=1 ---
|
2
|
-
import os
|
3
|
-
|
4
|
-
def _should_debug():
|
5
|
-
return os.environ.get("SWARM_DEBUG") == "1"
|
6
|
-
|
7
|
-
def _debug_print(*args, **kwargs):
|
8
|
-
if _should_debug():
|
9
|
-
print(*args, **kwargs)
|
10
|
-
|
11
|
-
def _framework_print(*args, **kwargs):
|
12
|
-
if _should_debug():
|
13
|
-
print(*args, **kwargs)
|
14
|
-
|
15
|
-
# --- Content for src/swarm/extensions/blueprint/blueprint_base.py ---
|
16
|
-
import logging
|
17
|
-
import json
|
18
|
-
from abc import ABC, abstractmethod
|
19
|
-
from typing import Dict, Any, Optional, List, AsyncGenerator
|
20
|
-
from pathlib import Path
|
21
|
-
from django.apps import apps # Import Django apps registry
|
22
|
-
|
23
|
-
# Keep the function import
|
24
|
-
from swarm.extensions.config.config_loader import get_profile_from_config, _substitute_env_vars
|
25
|
-
|
26
|
-
from openai import AsyncOpenAI
|
27
|
-
from agents import set_default_openai_client
|
28
|
-
from .slash_commands import slash_registry, SlashCommandRegistry
|
29
|
-
from blueprint_agents import * # Import all from blueprint_agents
|
30
|
-
|
31
|
-
logger = logging.getLogger(__name__)
|
32
|
-
from rich.console import Console
|
33
|
-
import traceback
|
34
|
-
|
35
|
-
# --- PATCH: Suppress OpenAI tracing/telemetry errors if using LiteLLM/custom endpoint ---
|
36
|
-
import logging
|
37
|
-
import os
|
38
|
-
if os.environ.get("LITELLM_BASE_URL") or os.environ.get("OPENAI_BASE_URL"):
|
39
|
-
# Silence openai.agents tracing/telemetry errors
|
40
|
-
logging.getLogger("openai.agents").setLevel(logging.CRITICAL)
|
41
|
-
try:
|
42
|
-
import openai.agents.tracing
|
43
|
-
openai.agents.tracing.TracingClient = lambda *a, **kw: None
|
44
|
-
except Exception:
|
45
|
-
pass
|
46
|
-
|
47
|
-
# --- Spinner/Status Message Enhancements ---
|
48
|
-
# To be used by all blueprints for consistent UX
|
49
|
-
import itertools
|
50
|
-
import sys
|
51
|
-
import threading
|
52
|
-
import time
|
53
|
-
|
54
|
-
class Spinner:
|
55
|
-
def __init__(self, message_sequence=None, interval=0.3, slow_threshold=10):
|
56
|
-
self.message_sequence = message_sequence or ['Generating.', 'Generating..', 'Generating...', 'Running...']
|
57
|
-
self.interval = interval
|
58
|
-
self.slow_threshold = slow_threshold # seconds before 'Taking longer than expected'
|
59
|
-
self._stop_event = threading.Event()
|
60
|
-
self._thread = None
|
61
|
-
self._start_time = None
|
62
|
-
|
63
|
-
def start(self):
|
64
|
-
self._stop_event.clear()
|
65
|
-
self._start_time = time.time()
|
66
|
-
self._thread = threading.Thread(target=self._spin)
|
67
|
-
self._thread.start()
|
68
|
-
|
69
|
-
def _spin(self):
|
70
|
-
for msg in itertools.cycle(self.message_sequence):
|
71
|
-
if self._stop_event.is_set():
|
72
|
-
break
|
73
|
-
elapsed = time.time() - self._start_time
|
74
|
-
if elapsed > self.slow_threshold:
|
75
|
-
sys.stdout.write('\rGenerating... Taking longer than expected ')
|
76
|
-
else:
|
77
|
-
sys.stdout.write(f'\r{msg} ')
|
78
|
-
sys.stdout.flush()
|
79
|
-
time.sleep(self.interval)
|
80
|
-
sys.stdout.write('\r')
|
81
|
-
sys.stdout.flush()
|
82
|
-
|
83
|
-
def stop(self, final_message=''):
|
84
|
-
self._stop_event.set()
|
85
|
-
if self._thread:
|
86
|
-
self._thread.join()
|
87
|
-
if final_message:
|
88
|
-
sys.stdout.write(f'\r{final_message}\n')
|
89
|
-
sys.stdout.flush()
|
90
|
-
|
91
|
-
# Usage Example (to be called in blueprints):
|
92
|
-
# spinner = Spinner()
|
93
|
-
# spinner.start()
|
94
|
-
# ... do work ...
|
95
|
-
# spinner.stop('Done!')
|
96
|
-
|
97
|
-
def configure_openai_client_from_env():
|
98
|
-
"""
|
99
|
-
Framework-level function: Always instantiate and set the default OpenAI client.
|
100
|
-
Prints out the config being used for debug.
|
101
|
-
"""
|
102
|
-
import os
|
103
|
-
from agents import set_default_openai_client
|
104
|
-
from openai import AsyncOpenAI
|
105
|
-
base_url = os.environ.get("LITELLM_BASE_URL") or os.environ.get("OPENAI_BASE_URL")
|
106
|
-
api_key = os.environ.get("LITELLM_API_KEY") or os.environ.get("OPENAI_API_KEY")
|
107
|
-
_debug_print(f"[DEBUG] Using OpenAI client config: base_url={base_url}, api_key={'set' if api_key else 'NOT SET'}")
|
108
|
-
if base_url and api_key:
|
109
|
-
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
|
110
|
-
set_default_openai_client(client)
|
111
|
-
_framework_print(f"[FRAMEWORK] Set default OpenAI client: base_url={base_url}, api_key={'set' if api_key else 'NOT SET'}")
|
112
|
-
else:
|
113
|
-
_framework_print("[FRAMEWORK] WARNING: base_url or api_key missing, OpenAI client not set!")
|
114
|
-
|
115
|
-
configure_openai_client_from_env()
|
116
|
-
|
117
|
-
class BlueprintBase(ABC):
|
118
|
-
"""
|
119
|
-
Abstract base class for all Swarm blueprints.
|
120
|
-
|
121
|
-
Defines the core interface for blueprint initialization and execution.
|
122
|
-
"""
|
123
|
-
enable_terminal_commands: bool = False # By default, terminal command execution is disabled
|
124
|
-
|
125
|
-
@classmethod
|
126
|
-
def main(cls):
|
127
|
-
"""
|
128
|
-
Standard CLI entry point for all blueprints.
|
129
|
-
Subclasses can override metadata/config_path if needed.
|
130
|
-
"""
|
131
|
-
from swarm.extensions.blueprint.cli_handler import run_blueprint_cli
|
132
|
-
from pathlib import Path
|
133
|
-
swarm_version = getattr(cls, "SWARM_VERSION", "1.0.0")
|
134
|
-
config_path = getattr(cls, "DEFAULT_CONFIG_PATH", Path(__file__).parent / "swarm_config.json")
|
135
|
-
run_blueprint_cli(cls, swarm_version=swarm_version, default_config_path=config_path)
|
136
|
-
|
137
|
-
def display_splash_screen(self, animated: bool = False):
|
138
|
-
"""Default splash screen. Subclasses can override for custom CLI/API branding."""
|
139
|
-
console = Console()
|
140
|
-
console.print(f"[bold cyan]Welcome to {self.__class__.__name__}![/]", style="bold")
|
141
|
-
|
142
|
-
def __init__(self, blueprint_id: str, config_path: Optional[Path] = None, enable_terminal_commands: Optional[bool] = None):
|
143
|
-
try:
|
144
|
-
if not blueprint_id:
|
145
|
-
raise ValueError("blueprint_id cannot be empty or None")
|
146
|
-
self.blueprint_id = blueprint_id
|
147
|
-
self.config_path = config_path # Note: config_path is currently unused if we rely on AppConfig
|
148
|
-
self._config: Optional[Dict[str, Any]] = None
|
149
|
-
self._llm_profile_name: Optional[str] = None
|
150
|
-
self._llm_profile_data: Optional[Dict[str, Any]] = None
|
151
|
-
self._markdown_output: bool = True # Default
|
152
|
-
# Allow per-instance override
|
153
|
-
if enable_terminal_commands is not None:
|
154
|
-
self.enable_terminal_commands = enable_terminal_commands
|
155
|
-
# Else: use class attribute (default False or set by subclass)
|
156
|
-
|
157
|
-
logger.info(f"Initializing blueprint '{self.blueprint_id}' (Type: {self.__class__.__name__})")
|
158
|
-
|
159
|
-
# --- Ensure custom OpenAI client for custom LLM providers ---
|
160
|
-
import os
|
161
|
-
|
162
|
-
# Remove monkey patching and envvar hacks. Always pass config values directly.
|
163
|
-
# (Retain only explicit AsyncOpenAI client instantiation in blueprints)
|
164
|
-
# (No changes needed here for direct client pattern)
|
165
|
-
|
166
|
-
self._load_and_process_config()
|
167
|
-
except AttributeError as e:
|
168
|
-
logger.debug(f"[BlueprintBase.__init__] AttributeError: {e}")
|
169
|
-
traceback.print_exc()
|
170
|
-
raise
|
171
|
-
|
172
|
-
def _load_and_process_config(self):
|
173
|
-
"""Loads the main Swarm config and extracts relevant settings. Falls back to empty config if Django unavailable or not found."""
|
174
|
-
import os
|
175
|
-
import json
|
176
|
-
from pathlib import Path
|
177
|
-
def redact(val):
|
178
|
-
if not isinstance(val, str) or len(val) <= 4:
|
179
|
-
return "****"
|
180
|
-
return val[:2] + "*" * (len(val)-4) + val[-2:]
|
181
|
-
def redact_dict(d):
|
182
|
-
if isinstance(d, dict):
|
183
|
-
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()}
|
184
|
-
elif isinstance(d, list):
|
185
|
-
return [redact_dict(item) for item in d]
|
186
|
-
return d
|
187
|
-
try:
|
188
|
-
try:
|
189
|
-
# --- Get config from the AppConfig instance (Django) ---
|
190
|
-
app_config_instance = apps.get_app_config('swarm')
|
191
|
-
if not hasattr(app_config_instance, 'config') or not app_config_instance.config:
|
192
|
-
raise ValueError("AppConfig for 'swarm' does not have a valid 'config' attribute.")
|
193
|
-
config = app_config_instance.config
|
194
|
-
logger.debug("Loaded config from Django AppConfig.")
|
195
|
-
except Exception as e:
|
196
|
-
if _should_debug():
|
197
|
-
logger.warning(f"Falling back to CLI/home config due to error: {e}")
|
198
|
-
config = None
|
199
|
-
# 1. CLI argument (not handled here, handled in cli_handler)
|
200
|
-
# 2. Current working directory
|
201
|
-
cwd_config = Path.cwd() / "swarm_config.json"
|
202
|
-
if cwd_config.exists():
|
203
|
-
with open(cwd_config, 'r') as f:
|
204
|
-
config = json.load(f)
|
205
|
-
# 3. XDG_CONFIG_HOME or ~/.config/swarm/swarm_config.json
|
206
|
-
elif os.environ.get("XDG_CONFIG_HOME"):
|
207
|
-
xdg_config = Path(os.environ["XDG_CONFIG_HOME"]) / "swarm" / "swarm_config.json"
|
208
|
-
if xdg_config.exists():
|
209
|
-
with open(xdg_config, 'r') as f:
|
210
|
-
config = json.load(f)
|
211
|
-
elif (Path.home() / ".config/swarm/swarm_config.json").exists():
|
212
|
-
with open(Path.home() / ".config/swarm/swarm_config.json", 'r') as f:
|
213
|
-
config = json.load(f)
|
214
|
-
# 4. Legacy fallback: ~/.swarm/swarm_config.json
|
215
|
-
elif (Path.home() / ".swarm/swarm_config.json").exists():
|
216
|
-
with open(Path.home() / ".swarm/swarm_config.json", 'r') as f:
|
217
|
-
config = json.load(f)
|
218
|
-
# 5. Fallback: OPENAI_API_KEY envvar
|
219
|
-
elif os.environ.get("OPENAI_API_KEY"):
|
220
|
-
config = {
|
221
|
-
"llm": {"default": {"provider": "openai", "model": "gpt-3.5-turbo", "api_key": os.environ["OPENAI_API_KEY"]}},
|
222
|
-
"settings": {"default_llm_profile": "default", "default_markdown_output": True},
|
223
|
-
"blueprints": {},
|
224
|
-
"llm_profile": "default",
|
225
|
-
"mcpServers": {}
|
226
|
-
}
|
227
|
-
logger.info("No config file found, using default config with OPENAI_API_KEY for CLI mode.")
|
228
|
-
else:
|
229
|
-
config = {}
|
230
|
-
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.")
|
231
|
-
if config is not None:
|
232
|
-
config = _substitute_env_vars(config)
|
233
|
-
self._config = config or {}
|
234
|
-
|
235
|
-
# --- After config is loaded, set OpenAI client from config if possible ---
|
236
|
-
try:
|
237
|
-
llm_profiles = self._config.get("llm", {})
|
238
|
-
default_profile = llm_profiles.get("default", {})
|
239
|
-
base_url = default_profile.get("base_url")
|
240
|
-
api_key = default_profile.get("api_key")
|
241
|
-
# Expand env vars if present
|
242
|
-
import os
|
243
|
-
if base_url and base_url.startswith("${"):
|
244
|
-
var = base_url[2:-1]
|
245
|
-
base_url = os.environ.get(var, base_url)
|
246
|
-
if api_key and api_key.startswith("${"):
|
247
|
-
var = api_key[2:-1]
|
248
|
-
api_key = os.environ.get(var, api_key)
|
249
|
-
if base_url and api_key:
|
250
|
-
from openai import AsyncOpenAI
|
251
|
-
from agents import set_default_openai_client
|
252
|
-
_debug_print(f"[DEBUG] (config) Setting OpenAI client: base_url={base_url}, api_key={'set' if api_key else 'NOT SET'}")
|
253
|
-
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
|
254
|
-
set_default_openai_client(client)
|
255
|
-
except Exception as e:
|
256
|
-
_debug_print(f"[DEBUG] Failed to set OpenAI client from config: {e}")
|
257
|
-
|
258
|
-
# --- Debug: Print and log redacted config ---
|
259
|
-
redacted_config = redact_dict(self._config)
|
260
|
-
logger.debug(f"Loaded config (redacted): {json.dumps(redacted_config, indent=2)}")
|
261
|
-
|
262
|
-
# --- Process LLM profile name and data ---
|
263
|
-
settings_section = self._config.get("settings", {})
|
264
|
-
llm_section = self._config.get("llm", {})
|
265
|
-
default_profile = settings_section.get("default_llm_profile") or "default"
|
266
|
-
self._llm_profile_name = self._config.get("llm_profile") or default_profile
|
267
|
-
if "profiles" in llm_section:
|
268
|
-
self._llm_profile_data = llm_section["profiles"].get(self._llm_profile_name, {})
|
269
|
-
else:
|
270
|
-
self._llm_profile_data = llm_section.get(self._llm_profile_name, {})
|
271
|
-
|
272
|
-
blueprint_specific_settings = self._config.get("blueprints", {}).get(self.blueprint_id, {})
|
273
|
-
global_markdown_setting = settings_section.get("default_markdown_output", True)
|
274
|
-
self._markdown_output = blueprint_specific_settings.get("markdown_output", global_markdown_setting)
|
275
|
-
logger.debug(f"Markdown output for '{self.blueprint_id}': {self._markdown_output}")
|
276
|
-
|
277
|
-
except ValueError as e:
|
278
|
-
logger.error(f"Configuration error for blueprint '{self.blueprint_id}': {e}", exc_info=True)
|
279
|
-
raise
|
280
|
-
except Exception as e:
|
281
|
-
logger.error(f"Unexpected error loading config for blueprint '{self.blueprint_id}': {e}", exc_info=True)
|
282
|
-
raise
|
283
|
-
|
284
|
-
@property
|
285
|
-
def config(self) -> Dict[str, Any]:
|
286
|
-
"""Returns the loaded and processed Swarm configuration."""
|
287
|
-
if self._config is None:
|
288
|
-
raise RuntimeError("Configuration accessed before initialization or after failure.")
|
289
|
-
return self._config
|
290
|
-
|
291
|
-
@property
|
292
|
-
def llm_profile(self) -> Dict[str, Any]:
|
293
|
-
"""Returns the loaded and processed LLM profile data for this blueprint."""
|
294
|
-
if self._llm_profile_data is None:
|
295
|
-
raise RuntimeError("LLM profile accessed before initialization or after failure.")
|
296
|
-
return self._llm_profile_data
|
297
|
-
|
298
|
-
@property
|
299
|
-
def llm_profile_name(self) -> str:
|
300
|
-
"""Returns the name of the LLM profile being used."""
|
301
|
-
if self._llm_profile_name is None:
|
302
|
-
raise RuntimeError("LLM profile name accessed before initialization or after failure.")
|
303
|
-
return self._llm_profile_name
|
304
|
-
|
305
|
-
@property
|
306
|
-
def slash_commands(self) -> SlashCommandRegistry:
|
307
|
-
"""Access the global slash command registry. Blueprints can register new commands here."""
|
308
|
-
return slash_registry
|
309
|
-
|
310
|
-
def get_llm_profile(self, profile_name: str) -> dict:
|
311
|
-
"""Returns the LLM profile dict for the given profile name from config, or empty dict if not found.
|
312
|
-
Supports both llm.profiles and direct llm keys for backward compatibility."""
|
313
|
-
llm_section = self.config.get("llm", {})
|
314
|
-
if "profiles" in llm_section:
|
315
|
-
return llm_section["profiles"].get(profile_name, {})
|
316
|
-
return llm_section.get(profile_name, {})
|
317
|
-
|
318
|
-
@property
|
319
|
-
def should_output_markdown(self) -> bool:
|
320
|
-
"""Returns whether the blueprint should format output as Markdown."""
|
321
|
-
return self._markdown_output
|
322
|
-
|
323
|
-
@abstractmethod
|
324
|
-
async def run(self, messages: List[Dict[str, Any]], **kwargs: Any) -> AsyncGenerator[Dict[str, Any], None]:
|
325
|
-
"""
|
326
|
-
The main execution method for the blueprint.
|
327
|
-
"""
|
328
|
-
import os
|
329
|
-
import pprint
|
330
|
-
logger.debug("ENVIRONMENT DUMP BEFORE MODEL CALL:")
|
331
|
-
pprint.pprint(dict(os.environ))
|
332
|
-
raise NotImplementedError("Subclasses must implement the 'run' method.")
|
333
|
-
yield {}
|
@@ -1,128 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import importlib
|
3
|
-
import importlib.util
|
4
|
-
import inspect
|
5
|
-
import logging # Ensure logging is imported
|
6
|
-
import sys
|
7
|
-
from typing import Dict, Type, Any
|
8
|
-
from pathlib import Path
|
9
|
-
|
10
|
-
# *** Define logger EARLIER ***
|
11
|
-
logger = logging.getLogger(__name__)
|
12
|
-
|
13
|
-
# *** Import the ACTUAL BlueprintBase from the likely correct path ***
|
14
|
-
try:
|
15
|
-
# Adjust this path if BlueprintBase lives elsewhere
|
16
|
-
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
17
|
-
except ImportError:
|
18
|
-
# This logger call is now safe
|
19
|
-
logger.error("Failed to import BlueprintBase from swarm.extensions.blueprint.blueprint_base. Using placeholder.", exc_info=True)
|
20
|
-
class BlueprintBase: # Fallback placeholder
|
21
|
-
metadata: Dict[str, Any] = {}
|
22
|
-
def __init__(self, *args, **kwargs): pass
|
23
|
-
async def run(self, *args, **kwargs): pass
|
24
|
-
|
25
|
-
|
26
|
-
class BlueprintLoadError(Exception):
|
27
|
-
"""Custom exception for errors during blueprint loading."""
|
28
|
-
pass
|
29
|
-
|
30
|
-
def _get_blueprint_name_from_dir(dir_name: str) -> str:
|
31
|
-
"""Converts directory name (e.g., 'blueprint_my_agent') to blueprint name (e.g., 'my_agent')."""
|
32
|
-
prefix = "blueprint_"
|
33
|
-
if dir_name.startswith(prefix):
|
34
|
-
return dir_name[len(prefix):]
|
35
|
-
return dir_name
|
36
|
-
|
37
|
-
def discover_blueprints(blueprint_dir: str) -> Dict[str, Type[BlueprintBase]]:
|
38
|
-
"""
|
39
|
-
Discovers blueprints (subclasses of BlueprintBase) by looking for
|
40
|
-
'blueprint_{name}.py' files within subdirectories of the given blueprint directory.
|
41
|
-
|
42
|
-
Args:
|
43
|
-
blueprint_dir: The path to the directory containing blueprint subdirectories.
|
44
|
-
|
45
|
-
Returns:
|
46
|
-
A dictionary mapping blueprint names to their corresponding class objects.
|
47
|
-
"""
|
48
|
-
logger.info(f"Starting blueprint discovery in directory: {blueprint_dir}")
|
49
|
-
blueprints: Dict[str, Type[BlueprintBase]] = {}
|
50
|
-
base_dir = Path(blueprint_dir).resolve()
|
51
|
-
|
52
|
-
if not base_dir.is_dir():
|
53
|
-
logger.error(f"Blueprint directory not found or is not a directory: {base_dir}")
|
54
|
-
return blueprints
|
55
|
-
|
56
|
-
# Iterate over items inside the base blueprint directory
|
57
|
-
for subdir in base_dir.iterdir():
|
58
|
-
if not subdir.is_dir():
|
59
|
-
continue # Skip files directly under blueprints/
|
60
|
-
|
61
|
-
# Use directory name as blueprint name (e.g., 'echocraft')
|
62
|
-
blueprint_name = subdir.name
|
63
|
-
logger.debug(f"Processing potential blueprint '{blueprint_name}' in directory: {subdir.name}")
|
64
|
-
|
65
|
-
# Look for the specific .py file, e.g., blueprint_echocraft.py
|
66
|
-
py_file_name = f"blueprint_{blueprint_name}.py"
|
67
|
-
py_file_path = subdir / py_file_name
|
68
|
-
|
69
|
-
if not py_file_path.is_file():
|
70
|
-
# Also check for just {blueprint_name}.py if that's a convention
|
71
|
-
alt_py_file_name = f"{blueprint_name}.py"
|
72
|
-
alt_py_file_path = subdir / alt_py_file_name
|
73
|
-
if alt_py_file_path.is_file():
|
74
|
-
py_file_path = alt_py_file_path # Use the alternative path
|
75
|
-
py_file_name = alt_py_file_name
|
76
|
-
logger.debug(f"Found alternative blueprint file: {py_file_name}")
|
77
|
-
else:
|
78
|
-
logger.warning(f"Skipping directory '{subdir.name}': Neither '{py_file_name}' nor '{alt_py_file_name}' found.")
|
79
|
-
continue
|
80
|
-
|
81
|
-
|
82
|
-
# Construct module import path, e.g., blueprints.echocraft.blueprint_echocraft
|
83
|
-
if py_file_path.name.startswith('blueprint_gatcha'):
|
84
|
-
module_import_path = f"swarm.blueprints.gatcha.{py_file_path.stem}"
|
85
|
-
elif py_file_path.name.startswith('blueprint_'):
|
86
|
-
module_import_path = f"swarm.blueprints.{subdir.name}.{py_file_path.stem}"
|
87
|
-
else:
|
88
|
-
continue
|
89
|
-
|
90
|
-
try:
|
91
|
-
# Ensure parent directory is in path
|
92
|
-
parent_dir = str(base_dir.parent)
|
93
|
-
if parent_dir not in sys.path:
|
94
|
-
logger.debug(f"Adding '{parent_dir}' to sys.path for blueprint discovery.")
|
95
|
-
sys.path.insert(0, parent_dir)
|
96
|
-
|
97
|
-
# Create module spec from file path
|
98
|
-
module_spec = importlib.util.spec_from_file_location(module_import_path, py_file_path)
|
99
|
-
|
100
|
-
if module_spec and module_spec.loader:
|
101
|
-
module = importlib.util.module_from_spec(module_spec)
|
102
|
-
sys.modules[module_import_path] = module
|
103
|
-
module_spec.loader.exec_module(module)
|
104
|
-
logger.debug(f"Successfully loaded module: {module_import_path}")
|
105
|
-
|
106
|
-
found_bp_class = None
|
107
|
-
for name, obj in inspect.getmembers(module):
|
108
|
-
if inspect.isclass(obj) and obj.__module__ == module_import_path and issubclass(obj, BlueprintBase) and obj is not BlueprintBase:
|
109
|
-
if found_bp_class:
|
110
|
-
logger.warning(f"Multiple BlueprintBase subclasses found in {py_file_name}. Using the first: {found_bp_class.__name__}.")
|
111
|
-
else:
|
112
|
-
logger.debug(f"Found Blueprint class '{name}' in module '{module_import_path}'")
|
113
|
-
found_bp_class = obj
|
114
|
-
blueprints[blueprint_name] = found_bp_class
|
115
|
-
# break
|
116
|
-
|
117
|
-
if not found_bp_class:
|
118
|
-
logger.warning(f"No BlueprintBase subclass found directly defined in module: {module_import_path}")
|
119
|
-
else:
|
120
|
-
logger.warning(f"Could not create module spec for {py_file_path}")
|
121
|
-
|
122
|
-
except Exception as e:
|
123
|
-
logger.error(f"Error processing blueprint file '{py_file_path}': {e}", exc_info=True)
|
124
|
-
if module_import_path in sys.modules:
|
125
|
-
del sys.modules[module_import_path]
|
126
|
-
|
127
|
-
logger.info(f"Blueprint discovery complete. Found: {list(blueprints.keys())}")
|
128
|
-
return blueprints
|
@@ -1,17 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Utility functions for blueprint management.
|
3
|
-
"""
|
4
|
-
|
5
|
-
def filter_blueprints(all_blueprints: dict, allowed_blueprints_str: str) -> dict:
|
6
|
-
"""
|
7
|
-
Filters the given blueprints dictionary using a comma-separated string of allowed blueprint keys.
|
8
|
-
|
9
|
-
Args:
|
10
|
-
all_blueprints (dict): A dictionary containing all discovered blueprints.
|
11
|
-
allowed_blueprints_str (str): A comma-separated string of allowed blueprint keys.
|
12
|
-
|
13
|
-
Returns:
|
14
|
-
dict: A dictionary containing only the blueprints whose keys are present in the allowed list.
|
15
|
-
"""
|
16
|
-
allowed_list = [bp.strip() for bp in allowed_blueprints_str.split(",")]
|
17
|
-
return {k: v for k, v in all_blueprints.items() if k in allowed_list}
|
@@ -1,12 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Common utilities potentially shared across blueprint extensions.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from typing import Any # Removed Dict, List as they weren't used
|
6
|
-
|
7
|
-
def get_agent_name(agent: Any) -> str:
|
8
|
-
"""Return the name of an agent from its attributes ('name' or '__name__')."""
|
9
|
-
return getattr(agent, "name", getattr(agent, "__name__", "<unknown>"))
|
10
|
-
|
11
|
-
# get_token_count has been moved to swarm.utils.context_utils
|
12
|
-
# Ensure imports in other files point to the correct location.
|