open-swarm 0.1.1745274976__py3-none-any.whl → 0.1.1748636259__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- open_swarm-0.1.1748636259.dist-info/METADATA +188 -0
- open_swarm-0.1.1748636259.dist-info/RECORD +82 -0
- {open_swarm-0.1.1745274976.dist-info → open_swarm-0.1.1748636259.dist-info}/WHEEL +2 -1
- open_swarm-0.1.1748636259.dist-info/entry_points.txt +3 -0
- open_swarm-0.1.1748636259.dist-info/top_level.txt +1 -0
- swarm/agent/agent.py +49 -0
- swarm/auth.py +48 -113
- swarm/consumers.py +0 -19
- swarm/extensions/blueprint/__init__.py +16 -30
- swarm/{core → extensions/blueprint}/agent_utils.py +1 -1
- swarm/extensions/blueprint/blueprint_base.py +458 -0
- swarm/extensions/blueprint/blueprint_discovery.py +112 -0
- swarm/extensions/blueprint/output_utils.py +95 -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 +201 -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/llm/chat_completion.py +195 -0
- swarm/serializers.py +5 -96
- swarm/settings.py +111 -99
- 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 +70 -237
- swarm/views/core_views.py +87 -80
- swarm/views/model_views.py +121 -64
- swarm/views/utils.py +441 -65
- swarm/views/web_views.py +2 -2
- open_swarm-0.1.1745274976.dist-info/METADATA +0 -874
- open_swarm-0.1.1745274976.dist-info/RECORD +0 -318
- open_swarm-0.1.1745274976.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 -97
- swarm/blueprints/geese/blueprint_geese.py +0 -803
- 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/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.1745274976.dist-info → open_swarm-0.1.1748636259.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
@@ -0,0 +1,458 @@
|
|
1
|
+
"""
|
2
|
+
Swarm Blueprint Base Module (Sync Interactive Mode) - Updated to Use openai-agents
|
3
|
+
|
4
|
+
This module provides the base class for blueprints with interactive and non-interactive modes.
|
5
|
+
It has been refactored to use the openai-agents Runner for agent execution instead of the legacy Swarm core.
|
6
|
+
Additionally, it initializes the mcp_servers attribute from the configuration and manages context variables.
|
7
|
+
Since the original swarm.types module has been removed, minimal ChatMessage and Response classes are defined here.
|
8
|
+
"""
|
9
|
+
|
10
|
+
import asyncio
|
11
|
+
import json
|
12
|
+
import logging
|
13
|
+
from abc import ABC, abstractmethod
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import Optional, Dict, Any, List
|
16
|
+
|
17
|
+
import os
|
18
|
+
import sys
|
19
|
+
import uuid
|
20
|
+
|
21
|
+
from swarm.extensions.config.config_loader import load_server_config
|
22
|
+
from swarm.settings import DEBUG
|
23
|
+
from swarm.utils.redact import redact_sensitive_data
|
24
|
+
from swarm.utils.message_sequence import repair_message_payload, validate_message_sequence
|
25
|
+
from swarm.utils.context_utils import truncate_message_history
|
26
|
+
from swarm.extensions.blueprint.agent_utils import get_agent_name, initialize_agents
|
27
|
+
from swarm.extensions.blueprint.django_utils import register_django_components
|
28
|
+
from swarm.extensions.blueprint.spinner import Spinner
|
29
|
+
from dotenv import load_dotenv
|
30
|
+
import argparse
|
31
|
+
|
32
|
+
class DummyMCPServer:
|
33
|
+
async def list_tools(self) -> list:
|
34
|
+
return []
|
35
|
+
|
36
|
+
# Import Runner and Agent from the openai-agents SDK.
|
37
|
+
from agents import Runner # type: ignore
|
38
|
+
from agents.agent import Agent # type: ignore
|
39
|
+
|
40
|
+
# Minimal definitions to replace swarm.types
|
41
|
+
from dataclasses import dataclass
|
42
|
+
|
43
|
+
@dataclass
|
44
|
+
class ChatMessage:
|
45
|
+
role: str
|
46
|
+
content: str
|
47
|
+
|
48
|
+
@dataclass
|
49
|
+
class Response:
|
50
|
+
messages: List[ChatMessage]
|
51
|
+
agent: Optional[Any]
|
52
|
+
context_variables: Dict[str, Any]
|
53
|
+
|
54
|
+
logger = logging.getLogger(__name__)
|
55
|
+
|
56
|
+
class BlueprintBase(ABC):
|
57
|
+
"""
|
58
|
+
Base class for blueprints using the openai-agents Runner for execution.
|
59
|
+
Agents are expected to be created via create_agents() and stored in self.agents.
|
60
|
+
Runner.run() is used to execute agents with a plain text input.
|
61
|
+
|
62
|
+
This version initializes mcp_servers from the configuration and restores context_variables.
|
63
|
+
"""
|
64
|
+
|
65
|
+
# Set up initial context variables as a class variable.
|
66
|
+
context_variables: Dict[str, Any] = {"user_goal": ""}
|
67
|
+
|
68
|
+
def __init__(
|
69
|
+
self,
|
70
|
+
config: dict,
|
71
|
+
auto_complete_task: bool = False,
|
72
|
+
update_user_goal: bool = False,
|
73
|
+
update_user_goal_frequency: int = 5,
|
74
|
+
skip_django_registration: bool = False,
|
75
|
+
record_chat: bool = False,
|
76
|
+
log_file_path: Optional[str] = None,
|
77
|
+
debug: bool = False,
|
78
|
+
use_markdown: bool = False,
|
79
|
+
**kwargs
|
80
|
+
):
|
81
|
+
self.auto_complete_task = auto_complete_task
|
82
|
+
self.update_user_goal = update_user_goal
|
83
|
+
self.update_user_goal_frequency = max(1, update_user_goal_frequency)
|
84
|
+
self.last_goal_update_count = 0
|
85
|
+
self.record_chat = record_chat
|
86
|
+
self.conversation_id = str(uuid.uuid4()) if record_chat else None
|
87
|
+
self.log_file_path = log_file_path
|
88
|
+
self.debug = debug or DEBUG
|
89
|
+
self.use_markdown = use_markdown
|
90
|
+
self._urls_registered = False # For Django URL registration
|
91
|
+
|
92
|
+
if self.use_markdown:
|
93
|
+
logger.debug("Markdown rendering enabled.")
|
94
|
+
logger.debug(f"Initializing {self.__class__.__name__} with config: {redact_sensitive_data(config)}")
|
95
|
+
if not hasattr(self, "metadata") or not isinstance(self.metadata, dict):
|
96
|
+
try:
|
97
|
+
_ = self.metadata
|
98
|
+
if not isinstance(self.metadata, dict):
|
99
|
+
raise TypeError("Metadata is not a dict")
|
100
|
+
except (AttributeError, NotImplementedError, TypeError) as e:
|
101
|
+
raise AssertionError(f"{self.__class__.__name__} must define a 'metadata' property returning a dictionary. Error: {e}")
|
102
|
+
|
103
|
+
self.truncation_mode = os.getenv("SWARM_TRUNCATION_MODE", "pairs").lower()
|
104
|
+
meta = self.metadata
|
105
|
+
self.max_context_tokens = max(1, meta.get("max_context_tokens", 8000))
|
106
|
+
self.max_context_messages = max(1, meta.get("max_context_messages", 50))
|
107
|
+
logger.debug(f"Truncation settings: mode={self.truncation_mode}, max_tokens={self.max_context_tokens}, max_messages={self.max_context_messages}")
|
108
|
+
|
109
|
+
load_dotenv()
|
110
|
+
logger.debug("Loaded environment variables from .env (if present).")
|
111
|
+
|
112
|
+
self.config = config
|
113
|
+
# Initialize mcp_servers from configuration.
|
114
|
+
self.mcp_servers: Dict[str, Any] = {}
|
115
|
+
if "mcp_servers" in self.config:
|
116
|
+
self.mcp_servers = load_server_config(self.config["mcp_servers"])
|
117
|
+
logger.debug(f"Loaded mcp_servers: {list(self.mcp_servers.keys())}")
|
118
|
+
else:
|
119
|
+
logger.debug("No mcp_servers configuration found.")
|
120
|
+
|
121
|
+
# Set default mcp server configurations if keys are missing.
|
122
|
+
if "mcp_llms_txt_server" not in self.mcp_servers:
|
123
|
+
logger.warning("mcp_llms_txt_server not found in mcp_servers; using default dummy configuration.")
|
124
|
+
self.mcp_servers["mcp_llms_txt_server"] = {"command": "echo", "args": [], "env": {}}
|
125
|
+
if "everything_server" not in self.mcp_servers:
|
126
|
+
logger.warning("everything_server not found in mcp_servers; using default dummy configuration.")
|
127
|
+
self.mcp_servers["everything_server"] = {"command": "echo", "args": [], "env": {}}
|
128
|
+
|
129
|
+
self.skip_django_registration = skip_django_registration or not os.environ.get("DJANGO_SETTINGS_MODULE")
|
130
|
+
# Initialize agents.
|
131
|
+
initialized_agents = initialize_agents(self) # type: ignore
|
132
|
+
self.agents: Dict[str, Agent] = initialized_agents if initialized_agents is not None else {}
|
133
|
+
# Restore context_variables on the instance.
|
134
|
+
self.context_variables = {"user_goal": ""}
|
135
|
+
self.starting_agent: Optional[Agent] = None
|
136
|
+
|
137
|
+
self._discovered_tools: Dict[str, List[Any]] = {}
|
138
|
+
self._discovered_resources: Dict[str, List[Any]] = {}
|
139
|
+
self.spinner = Spinner(interactive=not kwargs.get("non_interactive", False))
|
140
|
+
|
141
|
+
required_env_vars = set(meta.get("env_vars", []))
|
142
|
+
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
143
|
+
if missing_vars:
|
144
|
+
logger.warning(f"Missing environment variables for {meta.get('title', self.__class__.__name__)}: {', '.join(missing_vars)}")
|
145
|
+
|
146
|
+
self.required_mcp_servers = meta.get("required_mcp_servers", [])
|
147
|
+
logger.debug(f"Required MCP servers from metadata: {self.required_mcp_servers}")
|
148
|
+
|
149
|
+
if self._is_create_agents_overridden():
|
150
|
+
initialized_agents = initialize_agents(self) # type: ignore
|
151
|
+
self.agents = initialized_agents if initialized_agents is not None else {}
|
152
|
+
register_django_components(self)
|
153
|
+
|
154
|
+
def _is_create_agents_overridden(self) -> bool:
|
155
|
+
return getattr(self.__class__, "create_agents") is not getattr(BlueprintBase, "create_agents")
|
156
|
+
|
157
|
+
def truncate_message_history(self, messages: List[Dict[str, Any]], model: str) -> List[Dict[str, Any]]:
|
158
|
+
return truncate_message_history(messages, model, self.max_context_tokens, self.max_context_messages)
|
159
|
+
|
160
|
+
@property
|
161
|
+
@abstractmethod
|
162
|
+
def metadata(self) -> Dict[str, Any]:
|
163
|
+
raise NotImplementedError("Subclasses must implement the 'metadata' property.")
|
164
|
+
|
165
|
+
def create_agents(self) -> Dict[str, Agent]:
|
166
|
+
logger.debug(f"{self.__class__.__name__} using default create_agents (returns empty dict). Override if agents are needed.")
|
167
|
+
return {}
|
168
|
+
|
169
|
+
def set_starting_agent(self, agent: Agent) -> None:
|
170
|
+
agent_name = get_agent_name(agent) # type: ignore
|
171
|
+
logger.debug(f"Setting starting agent to: {agent_name}")
|
172
|
+
self.starting_agent = agent
|
173
|
+
# Only track the active agent if handoffs are defined (indicating async runner usage).
|
174
|
+
if hasattr(agent, "handoffs"):
|
175
|
+
self.context_variables["active_agent_name"] = agent_name
|
176
|
+
|
177
|
+
async def determine_active_agent(self) -> Optional[Agent]:
|
178
|
+
active_agent_name = self.context_variables.get("active_agent_name")
|
179
|
+
if active_agent_name and active_agent_name in self.agents:
|
180
|
+
logger.debug(f"Active agent determined: {active_agent_name}")
|
181
|
+
return self.agents[active_agent_name]
|
182
|
+
elif self.starting_agent is not None:
|
183
|
+
agent_to_use = self.starting_agent
|
184
|
+
starting_agent_name = get_agent_name(agent_to_use) # type: ignore
|
185
|
+
if active_agent_name != starting_agent_name:
|
186
|
+
logger.warning(f"Active agent name '{active_agent_name}' invalid; defaulting to starting agent: {starting_agent_name}")
|
187
|
+
self.context_variables["active_agent_name"] = starting_agent_name
|
188
|
+
else:
|
189
|
+
logger.debug(f"Using starting agent: {starting_agent_name}")
|
190
|
+
return agent_to_use
|
191
|
+
elif self.agents:
|
192
|
+
first_agent_name = next(iter(self.agents))
|
193
|
+
logger.warning(f"No active agent set. Defaulting to first registered agent: {first_agent_name}")
|
194
|
+
self.context_variables["active_agent_name"] = first_agent_name
|
195
|
+
return self.agents[first_agent_name]
|
196
|
+
else:
|
197
|
+
logger.error("No agents registered in blueprint.")
|
198
|
+
return None
|
199
|
+
|
200
|
+
def run_with_context(self, messages: List[Dict[str, Any]], context_variables: dict) -> dict:
|
201
|
+
dict_messages = []
|
202
|
+
for msg in messages:
|
203
|
+
if hasattr(msg, "model_dump"):
|
204
|
+
dict_messages.append(msg.model_dump(exclude_none=True)) # type: ignore
|
205
|
+
elif isinstance(msg, dict):
|
206
|
+
dict_messages.append(msg)
|
207
|
+
else:
|
208
|
+
logger.warning(f"Skipping non-dict message: {type(msg)}")
|
209
|
+
continue
|
210
|
+
return asyncio.run(self.run_with_context_async(dict_messages, context_variables))
|
211
|
+
|
212
|
+
async def run_with_context_async(self, messages: List[Dict[str, Any]], context_variables: dict) -> dict:
|
213
|
+
self.context_variables.update(context_variables)
|
214
|
+
logger.debug(f"Context variables updated: {list(self.context_variables.keys())}")
|
215
|
+
active_agent = await self.determine_active_agent()
|
216
|
+
if not active_agent:
|
217
|
+
logger.error("No active agent available.")
|
218
|
+
error_msg = ChatMessage(role="assistant", content="Error: No active agent available.")
|
219
|
+
return {
|
220
|
+
"response": Response(messages=[error_msg], agent=None, context_variables=self.context_variables),
|
221
|
+
"context_variables": self.context_variables
|
222
|
+
}
|
223
|
+
input_text = " ".join(msg.get("content", "") for msg in messages if "content" in msg)
|
224
|
+
if not input_text:
|
225
|
+
logger.warning("No valid input found in messages.")
|
226
|
+
input_text = ""
|
227
|
+
logger.debug(f"Running Runner with agent {get_agent_name(active_agent)} and input: {input_text[:100]}...")
|
228
|
+
self.spinner.start(f"Generating response from {get_agent_name(active_agent)}")
|
229
|
+
try:
|
230
|
+
result = await Runner.run(active_agent, input_text) # type: ignore
|
231
|
+
except Exception as e:
|
232
|
+
logger.error(f"Runner.run failed: {e}", exc_info=True)
|
233
|
+
error_msg = ChatMessage(role="assistant", content=f"Error: {str(e)}")
|
234
|
+
result = Response(messages=[error_msg], agent=active_agent, context_variables=self.context_variables)
|
235
|
+
finally:
|
236
|
+
self.spinner.stop()
|
237
|
+
updated_context = self.context_variables.copy()
|
238
|
+
return {"response": result, "context_variables": updated_context}
|
239
|
+
|
240
|
+
def set_active_agent(self, agent_name: str) -> None:
|
241
|
+
if agent_name in self.agents:
|
242
|
+
self.context_variables["active_agent_name"] = agent_name
|
243
|
+
logger.debug(f"Active agent set to: {agent_name}")
|
244
|
+
else:
|
245
|
+
logger.error(f"Agent '{agent_name}' not found. Available: {list(self.agents.keys())}")
|
246
|
+
|
247
|
+
async def _is_task_done_async(self, user_goal: str, conversation_summary: str, last_assistant_message: str) -> bool:
|
248
|
+
if not user_goal:
|
249
|
+
logger.warning("Empty user_goal; cannot check task completion.")
|
250
|
+
return False
|
251
|
+
system_prompt = os.getenv("TASK_DONE_PROMPT", "You are a completion checker. Answer ONLY YES or NO.")
|
252
|
+
user_prompt = os.getenv(
|
253
|
+
"TASK_DONE_USER_PROMPT",
|
254
|
+
"User's goal: {user_goal}\nConversation summary: {conversation_summary}\nLast assistant message: {last_assistant_message}\nIs the task complete? Answer only YES or NO."
|
255
|
+
).format(
|
256
|
+
user_goal=user_goal,
|
257
|
+
conversation_summary=conversation_summary,
|
258
|
+
last_assistant_message=last_assistant_message
|
259
|
+
)
|
260
|
+
check_prompt = [
|
261
|
+
{"role": "system", "content": system_prompt},
|
262
|
+
{"role": "user", "content": user_prompt}
|
263
|
+
]
|
264
|
+
client = Runner.client # type: ignore
|
265
|
+
model_to_use = Runner.current_llm_config.get("model", Runner.model) # type: ignore
|
266
|
+
try:
|
267
|
+
if not client:
|
268
|
+
raise ValueError("Runner client not available.")
|
269
|
+
response = await client.chat.completions.create(
|
270
|
+
model=model_to_use,
|
271
|
+
messages=check_prompt,
|
272
|
+
max_tokens=5,
|
273
|
+
temperature=0
|
274
|
+
)
|
275
|
+
if response.choices:
|
276
|
+
result_content = response.choices[0].message.content.strip().upper()
|
277
|
+
is_done = result_content.startswith("YES")
|
278
|
+
logger.debug(f"Task completion check: {is_done} (raw: '{result_content}')")
|
279
|
+
return is_done
|
280
|
+
else:
|
281
|
+
logger.warning("No choices in LLM response for task completion check.")
|
282
|
+
return False
|
283
|
+
except Exception as e:
|
284
|
+
logger.error(f"Task completion check failed: {e}", exc_info=True)
|
285
|
+
return False
|
286
|
+
|
287
|
+
async def _update_user_goal_async(self, messages: List[Dict[str, Any]]) -> None:
|
288
|
+
if not messages:
|
289
|
+
logger.debug("No messages provided for goal update.")
|
290
|
+
return
|
291
|
+
system_prompt = os.getenv(
|
292
|
+
"UPDATE_GOAL_PROMPT",
|
293
|
+
"Summarize the user's primary objective from the conversation in one sentence."
|
294
|
+
)
|
295
|
+
conversation_text = "\n".join(
|
296
|
+
f"{m.get('sender', m.get('role', ''))}: {m.get('content', '') or '[Tool Call]'}"
|
297
|
+
for m in messages if m.get("content") or m.get("tool_calls")
|
298
|
+
)
|
299
|
+
if not conversation_text:
|
300
|
+
logger.debug("No usable conversation content for goal update.")
|
301
|
+
return
|
302
|
+
user_prompt = os.getenv(
|
303
|
+
"UPDATE_GOAL_USER_PROMPT",
|
304
|
+
"Summarize the user's main goal based on this conversation:\n{conversation}"
|
305
|
+
).format(conversation=conversation_text[-2000:])
|
306
|
+
prompt = [
|
307
|
+
{"role": "system", "content": system_prompt},
|
308
|
+
{"role": "user", "content": user_prompt}
|
309
|
+
]
|
310
|
+
client = Runner.client # type: ignore
|
311
|
+
model_to_use = Runner.current_llm_config.get("model", Runner.model) # type: ignore
|
312
|
+
try:
|
313
|
+
if not client:
|
314
|
+
raise ValueError("Runner client not available for goal update.")
|
315
|
+
response = await client.chat.completions.create(
|
316
|
+
model=model_to_use,
|
317
|
+
messages=prompt,
|
318
|
+
max_tokens=60,
|
319
|
+
temperature=0.3
|
320
|
+
)
|
321
|
+
if response.choices:
|
322
|
+
new_goal = response.choices[0].message.content.strip()
|
323
|
+
if new_goal and new_goal != self.context_variables.get("user_goal"):
|
324
|
+
self.context_variables["user_goal"] = new_goal
|
325
|
+
logger.info(f"Updated user goal: {new_goal}")
|
326
|
+
elif not new_goal:
|
327
|
+
logger.warning("LLM returned an empty goal for update.")
|
328
|
+
else:
|
329
|
+
logger.debug("LLM goal update produced the same goal.")
|
330
|
+
else:
|
331
|
+
logger.warning("No choices in LLM response for goal update.")
|
332
|
+
except Exception as e:
|
333
|
+
logger.error(f"Goal update failed: {e}", exc_info=True)
|
334
|
+
|
335
|
+
def task_completed(self, outcome: str) -> None:
|
336
|
+
print(f"\n\033[93m[System Task Outcome]\033[0m: {outcome}")
|
337
|
+
|
338
|
+
@property
|
339
|
+
def prompt(self) -> str:
|
340
|
+
active_agent_name = self.context_variables.get("active_agent_name")
|
341
|
+
active_agent = self.agents.get(active_agent_name) if active_agent_name else None
|
342
|
+
if active_agent:
|
343
|
+
return f"{get_agent_name(active_agent)} > "
|
344
|
+
else:
|
345
|
+
return "User: "
|
346
|
+
|
347
|
+
def interactive_mode(self, stream: bool = False) -> None:
|
348
|
+
try:
|
349
|
+
from .interactive_mode import run_interactive_mode
|
350
|
+
run_interactive_mode(self, stream)
|
351
|
+
except ImportError:
|
352
|
+
logger.critical("Failed to import interactive_mode runner.")
|
353
|
+
print("Error: Cannot start interactive mode.", file=sys.stderr)
|
354
|
+
|
355
|
+
def non_interactive_mode(self, instruction: str, stream: bool = False) -> None:
|
356
|
+
logger.debug(f"Starting non-interactive mode with instruction: {instruction}, stream={stream}")
|
357
|
+
try:
|
358
|
+
asyncio.run(self.non_interactive_mode_async(instruction, stream=stream))
|
359
|
+
except RuntimeError as e:
|
360
|
+
if "cannot be called from a running event loop" in str(e):
|
361
|
+
logger.error("Non-interactive mode cannot run within an async context.")
|
362
|
+
print("Error: Non-interactive mode cannot run within an async context.", file=sys.stderr)
|
363
|
+
else:
|
364
|
+
raise e
|
365
|
+
except Exception as e:
|
366
|
+
logger.error(f"Error during non-interactive mode: {e}", exc_info=True)
|
367
|
+
print(f"Error: {e}", file=sys.stderr)
|
368
|
+
|
369
|
+
async def non_interactive_mode_async(self, instruction: str, stream: bool = False) -> None:
|
370
|
+
logger.debug(f"Starting async non-interactive mode with instruction: {instruction}, stream={stream}")
|
371
|
+
if not self.agents:
|
372
|
+
logger.error("No agents available in blueprint instance.")
|
373
|
+
print("Error: No agents available.", file=sys.stderr)
|
374
|
+
return
|
375
|
+
|
376
|
+
print(f"--- {self.metadata.get('title', 'Blueprint')} Non-Interactive Mode ---")
|
377
|
+
instructions = [line.strip() for line in instruction.splitlines() if line.strip()]
|
378
|
+
if not instructions:
|
379
|
+
print("No valid instruction provided.")
|
380
|
+
return
|
381
|
+
|
382
|
+
messages: List[Dict[str, Any]] = [{"role": "user", "content": line} for line in instructions]
|
383
|
+
if not self.starting_agent:
|
384
|
+
if self.agents:
|
385
|
+
first_agent_name = next(iter(self.agents.keys()))
|
386
|
+
logger.warning(f"No starting agent set. Defaulting to first agent: {first_agent_name}")
|
387
|
+
self.set_starting_agent(self.agents[first_agent_name])
|
388
|
+
else:
|
389
|
+
logger.error("No starting agent set and no agents defined.")
|
390
|
+
print("Error: No agent available.", file=sys.stderr)
|
391
|
+
return
|
392
|
+
|
393
|
+
self.context_variables["user_goal"] = instruction
|
394
|
+
if "active_agent_name" not in self.context_variables:
|
395
|
+
self.context_variables["active_agent_name"] = get_agent_name(self.starting_agent) # type: ignore
|
396
|
+
|
397
|
+
if stream:
|
398
|
+
logger.debug("Running non-interactive in streaming mode.")
|
399
|
+
active_agent = await self.determine_active_agent()
|
400
|
+
if not active_agent:
|
401
|
+
return
|
402
|
+
response_generator = Runner.run(active_agent, " ".join(m.get("content", "") for m in messages)) # type: ignore
|
403
|
+
await self._process_and_print_streaming_response_async(response_generator)
|
404
|
+
if self.auto_complete_task:
|
405
|
+
logger.warning("Auto-completion with streaming is not fully supported.")
|
406
|
+
else:
|
407
|
+
logger.debug("Running non-interactive in non-streaming mode.")
|
408
|
+
input_text = " ".join(m.get("content", "") for m in messages)
|
409
|
+
result = await Runner.run(self.starting_agent, input_text) # type: ignore
|
410
|
+
if hasattr(result, "final_output"):
|
411
|
+
print(f"\nFinal response:\n{result.final_output}")
|
412
|
+
else:
|
413
|
+
print("Received unexpected response format.")
|
414
|
+
print("--- Execution Completed ---")
|
415
|
+
|
416
|
+
async def _process_and_print_streaming_response_async(self, response_generator) -> Optional[Dict[str, Any]]:
|
417
|
+
full_content = ""
|
418
|
+
async for chunk in response_generator:
|
419
|
+
if "error" in chunk:
|
420
|
+
logger.error(f"Streaming error: {chunk['error']}")
|
421
|
+
print(f"Error: {chunk['error']}", file=sys.stderr)
|
422
|
+
break
|
423
|
+
if "choices" in chunk:
|
424
|
+
for choice in chunk["choices"]:
|
425
|
+
delta = choice.get("delta", {}).get("content", "")
|
426
|
+
full_content += delta
|
427
|
+
print(delta, end="", flush=True)
|
428
|
+
print()
|
429
|
+
return None
|
430
|
+
|
431
|
+
async def _auto_complete_task_async(self, current_history: List[Dict[str, Any]], stream: bool) -> None:
|
432
|
+
logger.debug("Auto-complete task not implemented.")
|
433
|
+
pass
|
434
|
+
|
435
|
+
@classmethod
|
436
|
+
def main(cls):
|
437
|
+
parser = argparse.ArgumentParser(description=f"Run the {cls.__name__} blueprint.")
|
438
|
+
parser.add_argument("--config", default="./swarm_config.json", help="Path to the configuration file.")
|
439
|
+
parser.add_argument("--instruction", help="Instruction for non-interactive mode.")
|
440
|
+
parser.add_argument("--stream", action="store_true", help="Enable streaming mode.")
|
441
|
+
args = parser.parse_args()
|
442
|
+
if not os.path.exists(args.config):
|
443
|
+
logger.error(f"Configuration file {args.config} not found.")
|
444
|
+
sys.exit(1)
|
445
|
+
try:
|
446
|
+
with open(args.config, "r") as f:
|
447
|
+
config = json.load(f)
|
448
|
+
except Exception as e:
|
449
|
+
logger.error(f"Failed to load configuration: {e}")
|
450
|
+
sys.exit(1)
|
451
|
+
blueprint_instance = cls(config=config)
|
452
|
+
if args.instruction:
|
453
|
+
blueprint_instance.non_interactive_mode(instruction=args.instruction, stream=args.stream)
|
454
|
+
else:
|
455
|
+
blueprint_instance.interactive_mode(stream=args.stream)
|
456
|
+
|
457
|
+
if __name__ == "__main__":
|
458
|
+
BlueprintBase.main()
|
@@ -0,0 +1,112 @@
|
|
1
|
+
"""
|
2
|
+
Blueprint Discovery Module for Open Swarm MCP.
|
3
|
+
|
4
|
+
This module dynamically discovers and imports blueprints from specified directories.
|
5
|
+
It identifies classes derived from BlueprintBase as valid blueprints and extracts their metadata.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import importlib.util
|
9
|
+
import inspect
|
10
|
+
import logging
|
11
|
+
import os
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import Dict, List, Any
|
14
|
+
from swarm.settings import DEBUG
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
18
|
+
|
19
|
+
try:
|
20
|
+
from .blueprint_base import BlueprintBase
|
21
|
+
except ImportError as e:
|
22
|
+
logger.critical(f"Failed to import BlueprintBase: {e}")
|
23
|
+
raise
|
24
|
+
|
25
|
+
def discover_blueprints(directories: List[str]) -> Dict[str, Dict[str, Any]]:
|
26
|
+
"""
|
27
|
+
Discover and load blueprints from specified directories.
|
28
|
+
Extract metadata including title, description, and other attributes.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
directories (List[str]): List of directories to search for blueprints.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
Dict[str, Dict[str, Any]]: Dictionary containing blueprint metadata.
|
35
|
+
"""
|
36
|
+
blueprints = {}
|
37
|
+
logger.info("Starting blueprint discovery.")
|
38
|
+
swarm_blueprints = os.getenv("SWARM_BLUEPRINTS", "").split(",")
|
39
|
+
if swarm_blueprints and swarm_blueprints[0]:
|
40
|
+
logger.debug(f"Filtering blueprints to: {swarm_blueprints}")
|
41
|
+
|
42
|
+
for directory in directories:
|
43
|
+
logger.debug(f"Searching for blueprints in: {directory}")
|
44
|
+
dir_path = Path(directory)
|
45
|
+
|
46
|
+
if not dir_path.exists() or not dir_path.is_dir():
|
47
|
+
logger.warning(f"Invalid directory: {directory}. Skipping...")
|
48
|
+
continue
|
49
|
+
|
50
|
+
for blueprint_file in dir_path.rglob("blueprint_*.py"):
|
51
|
+
module_name = blueprint_file.stem
|
52
|
+
blueprint_name = module_name.replace("blueprint_", "")
|
53
|
+
if swarm_blueprints and swarm_blueprints[0] and blueprint_name not in swarm_blueprints:
|
54
|
+
logger.debug(f"Skipping blueprint '{blueprint_name}' not in SWARM_BLUEPRINTS")
|
55
|
+
continue
|
56
|
+
module_path = str(blueprint_file.parent)
|
57
|
+
|
58
|
+
logger.debug(f"Found blueprint file: {blueprint_file}")
|
59
|
+
logger.debug(f"Module name: {module_name}, Blueprint name: {blueprint_name}, Module path: {module_path}")
|
60
|
+
|
61
|
+
try:
|
62
|
+
spec = importlib.util.spec_from_file_location(module_name, str(blueprint_file))
|
63
|
+
if spec is None or spec.loader is None:
|
64
|
+
logger.error(f"Cannot load module spec for blueprint file: {blueprint_file}. Skipping.")
|
65
|
+
continue
|
66
|
+
module = importlib.util.module_from_spec(spec)
|
67
|
+
spec.loader.exec_module(module)
|
68
|
+
logger.debug(f"Successfully imported module: {module_name}")
|
69
|
+
|
70
|
+
for name, obj in inspect.getmembers(module, inspect.isclass):
|
71
|
+
if not issubclass(obj, BlueprintBase) or obj is BlueprintBase:
|
72
|
+
continue
|
73
|
+
|
74
|
+
logger.debug(f"Discovered blueprint class: {name}")
|
75
|
+
|
76
|
+
try:
|
77
|
+
metadata = obj.metadata
|
78
|
+
if callable(metadata):
|
79
|
+
metadata = metadata()
|
80
|
+
elif isinstance(metadata, property):
|
81
|
+
if metadata.fget is not None:
|
82
|
+
metadata = metadata.fget(obj)
|
83
|
+
else:
|
84
|
+
logger.error(f"Blueprint '{blueprint_name}' property 'metadata' has no getter.")
|
85
|
+
raise ValueError(f"Blueprint '{blueprint_name}' metadata is inaccessible.")
|
86
|
+
|
87
|
+
if not isinstance(metadata, dict):
|
88
|
+
logger.error(f"Metadata for blueprint '{blueprint_name}' is not a dictionary.")
|
89
|
+
raise ValueError(f"Metadata for blueprint '{blueprint_name}' is invalid or inaccessible.")
|
90
|
+
|
91
|
+
if "title" not in metadata or "description" not in metadata:
|
92
|
+
logger.error(f"Required metadata fields (title, description) are missing for blueprint '{blueprint_name}'.")
|
93
|
+
raise ValueError(f"Metadata for blueprint '{blueprint_name}' is invalid or inaccessible.")
|
94
|
+
|
95
|
+
except Exception as e:
|
96
|
+
logger.error(f"Error retrieving metadata for blueprint '{blueprint_name}': {e}")
|
97
|
+
continue
|
98
|
+
|
99
|
+
blueprints[blueprint_name] = {
|
100
|
+
"blueprint_class": obj,
|
101
|
+
"title": metadata["title"],
|
102
|
+
"description": metadata["description"],
|
103
|
+
}
|
104
|
+
logger.debug(f"Added blueprint '{blueprint_name}' with metadata: {metadata}")
|
105
|
+
except ImportError as e:
|
106
|
+
logger.error(f"Failed to import module '{module_name}': {e}")
|
107
|
+
except Exception as e:
|
108
|
+
logger.error(f"Unexpected error importing '{module_name}': {e}", exc_info=True)
|
109
|
+
|
110
|
+
logger.info("Blueprint discovery complete.")
|
111
|
+
logger.debug(f"Discovered blueprints: {list(blueprints.keys())}")
|
112
|
+
return blueprints
|
@@ -0,0 +1,95 @@
|
|
1
|
+
"""
|
2
|
+
Output utilities for Swarm blueprints.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import logging
|
7
|
+
import sys
|
8
|
+
from typing import List, Dict, Any
|
9
|
+
|
10
|
+
# Optional import for markdown rendering
|
11
|
+
try:
|
12
|
+
from rich.markdown import Markdown
|
13
|
+
from rich.console import Console
|
14
|
+
RICH_AVAILABLE = True
|
15
|
+
except ImportError:
|
16
|
+
RICH_AVAILABLE = False
|
17
|
+
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
def render_markdown(content: str) -> None:
|
21
|
+
"""Render markdown content using rich, if available."""
|
22
|
+
# --- DEBUG PRINT ---
|
23
|
+
print(f"\n[DEBUG render_markdown called with rich={RICH_AVAILABLE}]", flush=True)
|
24
|
+
if not RICH_AVAILABLE:
|
25
|
+
print(content, flush=True) # Fallback print with flush
|
26
|
+
return
|
27
|
+
console = Console()
|
28
|
+
md = Markdown(content)
|
29
|
+
console.print(md) # Rich handles flushing
|
30
|
+
|
31
|
+
def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = False, spinner=None) -> None:
|
32
|
+
"""Format and print messages, optionally rendering assistant content as markdown."""
|
33
|
+
# --- DEBUG PRINT ---
|
34
|
+
print(f"\n[DEBUG pretty_print_response called with {len(messages)} messages, use_markdown={use_markdown}]", flush=True)
|
35
|
+
|
36
|
+
if spinner:
|
37
|
+
spinner.stop()
|
38
|
+
sys.stdout.write("\r\033[K") # Clear spinner line
|
39
|
+
sys.stdout.flush()
|
40
|
+
|
41
|
+
if not messages:
|
42
|
+
logger.debug("No messages to print in pretty_print_response.")
|
43
|
+
return
|
44
|
+
|
45
|
+
for i, msg in enumerate(messages):
|
46
|
+
# --- DEBUG PRINT ---
|
47
|
+
print(f"\n[DEBUG Processing message {i}: type={type(msg)}]", flush=True)
|
48
|
+
if not isinstance(msg, dict):
|
49
|
+
print(f"[DEBUG Skipping non-dict message {i}]", flush=True)
|
50
|
+
continue
|
51
|
+
|
52
|
+
role = msg.get("role")
|
53
|
+
sender = msg.get("sender", role if role else "Unknown")
|
54
|
+
msg_content = msg.get("content")
|
55
|
+
tool_calls = msg.get("tool_calls")
|
56
|
+
# --- DEBUG PRINT ---
|
57
|
+
print(f"[DEBUG Message {i}: role={role}, sender={sender}, has_content={bool(msg_content)}, has_tools={bool(tool_calls)}]", flush=True)
|
58
|
+
|
59
|
+
|
60
|
+
if role == "assistant":
|
61
|
+
print(f"\033[94m{sender}\033[0m: ", end="", flush=True)
|
62
|
+
if msg_content:
|
63
|
+
# --- DEBUG PRINT ---
|
64
|
+
print(f"\n[DEBUG Assistant content found, printing/rendering... Rich={RICH_AVAILABLE}, Markdown={use_markdown}]", flush=True)
|
65
|
+
if use_markdown and RICH_AVAILABLE:
|
66
|
+
render_markdown(msg_content)
|
67
|
+
else:
|
68
|
+
# --- DEBUG PRINT ---
|
69
|
+
print(f"\n[DEBUG Using standard print for content:]", flush=True)
|
70
|
+
print(msg_content, flush=True) # Added flush
|
71
|
+
elif not tool_calls:
|
72
|
+
print(flush=True) # Flush newline if no content/tools
|
73
|
+
|
74
|
+
if tool_calls and isinstance(tool_calls, list):
|
75
|
+
print(" \033[92mTool Calls:\033[0m", flush=True)
|
76
|
+
for tc in tool_calls:
|
77
|
+
if not isinstance(tc, dict): continue
|
78
|
+
func = tc.get("function", {})
|
79
|
+
tool_name = func.get("name", "Unnamed Tool")
|
80
|
+
args_str = func.get("arguments", "{}")
|
81
|
+
try: args_obj = json.loads(args_str); args_pretty = ", ".join(f"{k}={v!r}" for k, v in args_obj.items())
|
82
|
+
except json.JSONDecodeError: args_pretty = args_str
|
83
|
+
print(f" \033[95m{tool_name}\033[0m({args_pretty})", flush=True)
|
84
|
+
|
85
|
+
elif role == "tool":
|
86
|
+
tool_name = msg.get("tool_name", msg.get("name", "tool"))
|
87
|
+
tool_id = msg.get("tool_call_id", "N/A")
|
88
|
+
try: content_obj = json.loads(msg_content); pretty_content = json.dumps(content_obj, indent=2)
|
89
|
+
except (json.JSONDecodeError, TypeError): pretty_content = msg_content
|
90
|
+
print(f" \033[93m[{tool_name} Result ID: {tool_id}]\033[0m:\n {pretty_content.replace(chr(10), chr(10) + ' ')}", flush=True)
|
91
|
+
else:
|
92
|
+
# --- DEBUG PRINT ---
|
93
|
+
print(f"[DEBUG Skipping message {i} with role '{role}']", flush=True)
|
94
|
+
|
95
|
+
|