open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743364176__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.1743364176.dist-info/METADATA +286 -0
- open_swarm-0.1.1743364176.dist-info/RECORD +260 -0
- {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/WHEEL +1 -2
- open_swarm-0.1.1743364176.dist-info/entry_points.txt +2 -0
- swarm/__init__.py +0 -2
- swarm/auth.py +53 -49
- swarm/blueprints/README.md +67 -0
- swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
- swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
- swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
- swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
- swarm/blueprints/divine_code/__init__.py +0 -0
- swarm/blueprints/divine_code/apps.py +11 -0
- swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
- swarm/blueprints/django_chat/apps.py +6 -0
- swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
- swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
- swarm/blueprints/django_chat/urls.py +8 -0
- swarm/blueprints/django_chat/views.py +32 -0
- swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
- swarm/blueprints/family_ties/apps.py +11 -0
- swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
- swarm/blueprints/family_ties/models.py +19 -0
- swarm/blueprints/family_ties/serializers.py +7 -0
- swarm/blueprints/family_ties/settings.py +16 -0
- swarm/blueprints/family_ties/urls.py +10 -0
- swarm/blueprints/family_ties/views.py +26 -0
- swarm/blueprints/flock/__init__.py +0 -0
- swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
- swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
- swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
- swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
- swarm/blueprints/rue_code/__init__.py +0 -0
- swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
- swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
- swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
- swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
- swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
- swarm/extensions/blueprint/__init__.py +30 -15
- swarm/extensions/blueprint/agent_utils.py +16 -40
- swarm/extensions/blueprint/blueprint_base.py +141 -543
- swarm/extensions/blueprint/blueprint_discovery.py +112 -98
- swarm/extensions/blueprint/cli_handler.py +185 -0
- swarm/extensions/blueprint/config_loader.py +122 -0
- swarm/extensions/blueprint/django_utils.py +181 -79
- swarm/extensions/blueprint/interactive_mode.py +1 -1
- swarm/extensions/config/config_loader.py +83 -200
- swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
- swarm/extensions/launchers/swarm_cli.py +199 -287
- swarm/llm/chat_completion.py +26 -55
- swarm/management/__init__.py +0 -0
- swarm/management/commands/__init__.py +0 -0
- swarm/management/commands/runserver.py +58 -0
- swarm/permissions.py +38 -0
- swarm/serializers.py +96 -5
- swarm/settings.py +95 -110
- swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
- swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
- swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
- swarm/static/contrib/markedjs/marked.min.js +6 -0
- swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
- swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
- swarm/static/contrib/tabler-icons/archive.svg +21 -0
- swarm/static/contrib/tabler-icons/artboard.svg +27 -0
- swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
- swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
- swarm/static/contrib/tabler-icons/carambola.svg +19 -0
- swarm/static/contrib/tabler-icons/copy.svg +20 -0
- swarm/static/contrib/tabler-icons/download.svg +21 -0
- swarm/static/contrib/tabler-icons/edit.svg +21 -0
- swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
- swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
- swarm/static/contrib/tabler-icons/headset.svg +22 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
- swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
- swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
- swarm/static/contrib/tabler-icons/message-star.svg +22 -0
- swarm/static/contrib/tabler-icons/message-x.svg +23 -0
- swarm/static/contrib/tabler-icons/message.svg +21 -0
- swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
- swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
- swarm/static/contrib/tabler-icons/robot.svg +26 -0
- swarm/static/contrib/tabler-icons/search.svg +19 -0
- swarm/static/contrib/tabler-icons/settings.svg +20 -0
- swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
- swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
- swarm/static/css/dropdown.css +22 -0
- swarm/static/htmx/htmx.min.js +0 -0
- swarm/static/js/dropdown.js +23 -0
- swarm/static/rest_mode/css/base.css +470 -0
- swarm/static/rest_mode/css/chat-history.css +286 -0
- swarm/static/rest_mode/css/chat.css +251 -0
- swarm/static/rest_mode/css/chatbot.css +74 -0
- swarm/static/rest_mode/css/chatgpt.css +62 -0
- swarm/static/rest_mode/css/colors/corporate.css +74 -0
- swarm/static/rest_mode/css/colors/pastel.css +81 -0
- swarm/static/rest_mode/css/colors/tropical.css +82 -0
- swarm/static/rest_mode/css/general.css +142 -0
- swarm/static/rest_mode/css/layout.css +167 -0
- swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
- swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
- swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
- swarm/static/rest_mode/css/messages.css +84 -0
- swarm/static/rest_mode/css/messenger.css +135 -0
- swarm/static/rest_mode/css/settings.css +91 -0
- swarm/static/rest_mode/css/simple.css +44 -0
- swarm/static/rest_mode/css/slack.css +58 -0
- swarm/static/rest_mode/css/style.css +156 -0
- swarm/static/rest_mode/css/theme.css +30 -0
- swarm/static/rest_mode/css/toast.css +40 -0
- swarm/static/rest_mode/js/auth.js +9 -0
- swarm/static/rest_mode/js/blueprint.js +41 -0
- swarm/static/rest_mode/js/blueprintUtils.js +12 -0
- swarm/static/rest_mode/js/chatLogic.js +79 -0
- swarm/static/rest_mode/js/debug.js +63 -0
- swarm/static/rest_mode/js/events.js +98 -0
- swarm/static/rest_mode/js/main.js +19 -0
- swarm/static/rest_mode/js/messages.js +264 -0
- swarm/static/rest_mode/js/messengerLogic.js +355 -0
- swarm/static/rest_mode/js/modules/apiService.js +84 -0
- swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
- swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
- swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
- swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
- swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
- swarm/static/rest_mode/js/modules/state.js +7 -0
- swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
- swarm/static/rest_mode/js/modules/validation.js +23 -0
- swarm/static/rest_mode/js/rendering.js +119 -0
- swarm/static/rest_mode/js/settings.js +130 -0
- swarm/static/rest_mode/js/sidebar.js +94 -0
- swarm/static/rest_mode/js/simpleLogic.js +37 -0
- swarm/static/rest_mode/js/slackLogic.js +66 -0
- swarm/static/rest_mode/js/splash.js +76 -0
- swarm/static/rest_mode/js/theme.js +111 -0
- swarm/static/rest_mode/js/toast.js +36 -0
- swarm/static/rest_mode/js/ui.js +265 -0
- swarm/static/rest_mode/js/validation.js +57 -0
- swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
- swarm/static/rest_mode/svg/arrow_down.svg +5 -0
- swarm/static/rest_mode/svg/arrow_left.svg +5 -0
- swarm/static/rest_mode/svg/arrow_right.svg +5 -0
- swarm/static/rest_mode/svg/arrow_up.svg +5 -0
- swarm/static/rest_mode/svg/attach.svg +8 -0
- swarm/static/rest_mode/svg/avatar.svg +7 -0
- swarm/static/rest_mode/svg/canvas.svg +6 -0
- swarm/static/rest_mode/svg/chat_history.svg +4 -0
- swarm/static/rest_mode/svg/close.svg +5 -0
- swarm/static/rest_mode/svg/copy.svg +4 -0
- swarm/static/rest_mode/svg/dark_mode.svg +3 -0
- swarm/static/rest_mode/svg/edit.svg +5 -0
- swarm/static/rest_mode/svg/layout.svg +9 -0
- swarm/static/rest_mode/svg/logo.svg +29 -0
- swarm/static/rest_mode/svg/logout.svg +5 -0
- swarm/static/rest_mode/svg/mobile.svg +5 -0
- swarm/static/rest_mode/svg/new_chat.svg +4 -0
- swarm/static/rest_mode/svg/not_visible.svg +5 -0
- swarm/static/rest_mode/svg/plus.svg +7 -0
- swarm/static/rest_mode/svg/run_code.svg +6 -0
- swarm/static/rest_mode/svg/save.svg +4 -0
- swarm/static/rest_mode/svg/search.svg +6 -0
- swarm/static/rest_mode/svg/settings.svg +4 -0
- swarm/static/rest_mode/svg/speaker.svg +5 -0
- swarm/static/rest_mode/svg/stop.svg +6 -0
- swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
- swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
- swarm/static/rest_mode/svg/toggle_off.svg +6 -0
- swarm/static/rest_mode/svg/toggle_on.svg +6 -0
- swarm/static/rest_mode/svg/trash.svg +10 -0
- swarm/static/rest_mode/svg/undo.svg +3 -0
- swarm/static/rest_mode/svg/visible.svg +8 -0
- swarm/static/rest_mode/svg/voice.svg +10 -0
- swarm/templates/account/login.html +22 -0
- swarm/templates/account/signup.html +32 -0
- swarm/templates/base.html +30 -0
- swarm/templates/chat.html +43 -0
- swarm/templates/index.html +35 -0
- swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
- swarm/templates/rest_mode/components/header.html +45 -0
- swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
- swarm/templates/rest_mode/components/settings_dialog.html +97 -0
- swarm/templates/rest_mode/components/splash_screen.html +7 -0
- swarm/templates/rest_mode/components/top_bar.html +28 -0
- swarm/templates/rest_mode/message_ui.html +50 -0
- swarm/templates/rest_mode/slackbot.html +30 -0
- swarm/templates/simple_blueprint_page.html +24 -0
- swarm/templates/websocket_partials/final_system_message.html +3 -0
- swarm/templates/websocket_partials/system_message.html +4 -0
- swarm/templates/websocket_partials/user_message.html +5 -0
- swarm/urls.py +57 -74
- swarm/utils/log_utils.py +63 -0
- swarm/views/api_views.py +48 -39
- swarm/views/chat_views.py +156 -70
- swarm/views/core_views.py +85 -90
- swarm/views/model_views.py +64 -121
- swarm/views/utils.py +65 -441
- open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
- open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
- open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
- open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
- swarm/agent/agent.py +0 -49
- swarm/core.py +0 -326
- swarm/extensions/mcp/__init__.py +0 -1
- swarm/extensions/mcp/cache_utils.py +0 -36
- swarm/extensions/mcp/mcp_client.py +0 -341
- swarm/extensions/mcp/mcp_constants.py +0 -7
- swarm/extensions/mcp/mcp_tool_provider.py +0 -110
- swarm/types.py +0 -126
- {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from typing import Dict, Any, List, ClassVar, Optional
|
5
|
+
|
6
|
+
# Ensure src is in path for BlueprintBase import
|
7
|
+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
8
|
+
src_path = os.path.join(project_root, 'src')
|
9
|
+
if src_path not in sys.path: sys.path.insert(0, src_path)
|
10
|
+
|
11
|
+
try:
|
12
|
+
from agents import Agent, Tool, function_tool, Runner
|
13
|
+
from agents.mcp import MCPServer
|
14
|
+
from agents.models.interface import Model
|
15
|
+
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
16
|
+
from openai import AsyncOpenAI
|
17
|
+
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
18
|
+
except ImportError as e:
|
19
|
+
print(f"ERROR: Import failed in ChatbotBlueprint: {e}. Check dependencies.")
|
20
|
+
print(f"sys.path: {sys.path}")
|
21
|
+
sys.exit(1)
|
22
|
+
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
# --- Define the Blueprint ---
|
26
|
+
class ChatbotBlueprint(BlueprintBase):
|
27
|
+
"""A simple conversational chatbot agent."""
|
28
|
+
metadata: ClassVar[Dict[str, Any]] = {
|
29
|
+
"name": "ChatbotBlueprint",
|
30
|
+
"title": "Simple Chatbot",
|
31
|
+
"description": "A basic conversational agent that responds to user input.",
|
32
|
+
"version": "1.1.0", # Refactored version
|
33
|
+
"author": "Open Swarm Team (Refactored)",
|
34
|
+
"tags": ["chatbot", "conversation", "simple"],
|
35
|
+
"required_mcp_servers": [],
|
36
|
+
"env_vars": [],
|
37
|
+
}
|
38
|
+
|
39
|
+
# Caches
|
40
|
+
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
41
|
+
_model_instance_cache: Dict[str, Model] = {}
|
42
|
+
|
43
|
+
# --- Model Instantiation Helper --- (Standard helper)
|
44
|
+
def _get_model_instance(self, profile_name: str) -> Model:
|
45
|
+
"""Retrieves or creates an LLM Model instance."""
|
46
|
+
# ... (Implementation is the same as previous refactors) ...
|
47
|
+
if profile_name in self._model_instance_cache:
|
48
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
49
|
+
return self._model_instance_cache[profile_name]
|
50
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
51
|
+
profile_data = self.get_llm_profile(profile_name)
|
52
|
+
if not profile_data: raise ValueError(f"Missing LLM profile '{profile_name}'.")
|
53
|
+
provider = profile_data.get("provider", "openai").lower()
|
54
|
+
model_name = profile_data.get("model")
|
55
|
+
if not model_name: raise ValueError(f"Missing 'model' in profile '{profile_name}'.")
|
56
|
+
if provider != "openai": raise ValueError(f"Unsupported provider: {provider}")
|
57
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
58
|
+
if client_cache_key not in self._openai_client_cache:
|
59
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
60
|
+
filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
61
|
+
log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
|
62
|
+
logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}': {log_kwargs}")
|
63
|
+
try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
|
64
|
+
except Exception as e: raise ValueError(f"Failed to init client: {e}") from e
|
65
|
+
client = self._openai_client_cache[client_cache_key]
|
66
|
+
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
|
67
|
+
try:
|
68
|
+
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
|
69
|
+
self._model_instance_cache[profile_name] = model_instance
|
70
|
+
return model_instance
|
71
|
+
except Exception as e: raise ValueError(f"Failed to init LLM: {e}") from e
|
72
|
+
|
73
|
+
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
74
|
+
"""Creates the single Chatbot agent."""
|
75
|
+
logger.debug("Creating Chatbot agent...")
|
76
|
+
self._model_instance_cache = {}
|
77
|
+
self._openai_client_cache = {}
|
78
|
+
|
79
|
+
default_profile_name = self.config.get("llm_profile", "default")
|
80
|
+
logger.debug(f"Using LLM profile '{default_profile_name}' for Chatbot.")
|
81
|
+
model_instance = self._get_model_instance(default_profile_name)
|
82
|
+
|
83
|
+
chatbot_instructions = "You are a helpful and friendly chatbot. Respond directly to the user's input in a conversational manner."
|
84
|
+
|
85
|
+
chatbot_agent = Agent(
|
86
|
+
name="Chatbot",
|
87
|
+
model=model_instance,
|
88
|
+
instructions=chatbot_instructions,
|
89
|
+
tools=[], # No function tools needed for simple chat
|
90
|
+
mcp_servers=mcp_servers # Pass along, though likely unused
|
91
|
+
)
|
92
|
+
|
93
|
+
logger.debug("Chatbot agent created.")
|
94
|
+
return chatbot_agent
|
95
|
+
|
96
|
+
# Standard Python entry point
|
97
|
+
if __name__ == "__main__":
|
98
|
+
ChatbotBlueprint.main()
|
@@ -0,0 +1,33 @@
|
|
1
|
+
{% load static %}
|
2
|
+
<!DOCTYPE html>
|
3
|
+
<html lang="en">
|
4
|
+
<head>
|
5
|
+
{% include 'rest_mode/components/header.html' %}
|
6
|
+
<link rel="stylesheet" href="{% static 'rest_mode/css/chatbot.css' %}">
|
7
|
+
<script>
|
8
|
+
window.STATIC_URLS = {
|
9
|
+
layoutSidebarLeftExpand: "{% static 'contrib/tabler-icons/layout-sidebar-left-expand.svg' %}",
|
10
|
+
layoutSidebarLeftCollapse: "{% static 'contrib/tabler-icons/layout-sidebar-left-collapse.svg' %}"
|
11
|
+
};
|
12
|
+
</script>
|
13
|
+
</head>
|
14
|
+
<body>
|
15
|
+
{% include 'rest_mode/components/top_bar.html' %}
|
16
|
+
{% include 'rest_mode/components/settings_dialog.html' %}
|
17
|
+
<div class="container" data-theme-color="corporate" data-theme-dark="false" data-theme-layout="chatbot-layout">
|
18
|
+
<div class="side-panes chat-history-pane" id="chatHistoryPane">
|
19
|
+
{% include 'rest_mode/components/chat_sidebar.html' with conversations=conversations %}
|
20
|
+
</div>
|
21
|
+
<div class="vertical-divider" id="divider-left"></div>
|
22
|
+
<div class="main-pane">
|
23
|
+
{% include 'rest_mode/components/main_chat_pane.html' %}
|
24
|
+
</div>
|
25
|
+
<div id="toastContainer"></div>
|
26
|
+
</div>
|
27
|
+
<div class="settings-overlay"></div> <!-- Ensure overlay exists -->
|
28
|
+
<script type="module" src="{% static 'contrib/markedjs/marked.min.js' %}"></script>
|
29
|
+
<script type="module" src="{% static 'rest_mode/js/settings.js' %}" defer></script>
|
30
|
+
<script type="module" src="{% static 'rest_mode/js/main.js' %}"></script>
|
31
|
+
<script type="module" src="{% static 'rest_mode/js/chatLogic.js' %}" defer></script>
|
32
|
+
</body>
|
33
|
+
</html>
|
@@ -0,0 +1,183 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from typing import Dict, Any, List, ClassVar, Optional
|
5
|
+
|
6
|
+
# Ensure src is in path for BlueprintBase import
|
7
|
+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
8
|
+
src_path = os.path.join(project_root, 'src')
|
9
|
+
if src_path not in sys.path: sys.path.insert(0, src_path)
|
10
|
+
|
11
|
+
try:
|
12
|
+
from agents import Agent, Tool, function_tool, Runner # Added Runner
|
13
|
+
from agents.mcp import MCPServer
|
14
|
+
from agents.models.interface import Model
|
15
|
+
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
16
|
+
from openai import AsyncOpenAI
|
17
|
+
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
18
|
+
except ImportError as e:
|
19
|
+
print(f"ERROR: Import failed in DigitalButlersBlueprint: {e}. Check 'openai-agents' install and project structure.")
|
20
|
+
print(f"Attempted import from directory: {os.path.dirname(__file__)}")
|
21
|
+
print(f"sys.path: {sys.path}")
|
22
|
+
sys.exit(1)
|
23
|
+
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
# --- Agent Instructions ---
|
27
|
+
|
28
|
+
SHARED_INSTRUCTIONS = """
|
29
|
+
You are part of the Digital Butlers team. Collaborate via Jeeves, the coordinator.
|
30
|
+
Roles:
|
31
|
+
- Jeeves (Coordinator): User interface, planning, delegation via Agent Tools.
|
32
|
+
- Mycroft (Web Search): Uses `duckduckgo-search` MCP tool for private web searches.
|
33
|
+
- Gutenberg (Home Automation): Uses `home-assistant` MCP tool to control devices.
|
34
|
+
Respond ONLY to the agent who tasked you (typically Jeeves). Provide clear, concise results.
|
35
|
+
"""
|
36
|
+
|
37
|
+
jeeves_instructions = (
|
38
|
+
f"{SHARED_INSTRUCTIONS}\n\n"
|
39
|
+
"YOUR ROLE: Jeeves, the Coordinator. You are the primary interface with the user.\n"
|
40
|
+
"1. Understand the user's request fully.\n"
|
41
|
+
"2. If it involves searching the web, delegate the specific search query to the `Mycroft` agent tool.\n"
|
42
|
+
"3. If it involves controlling home devices (lights, switches, etc.), delegate the specific command (e.g., 'turn on kitchen light') to the `Gutenberg` agent tool.\n"
|
43
|
+
"4. If the request is simple and doesn't require search or home automation, answer it directly.\n"
|
44
|
+
"5. Synthesize the results received from Mycroft or Gutenberg into a polite, helpful, and complete response for the user. Do not just relay their raw output."
|
45
|
+
)
|
46
|
+
|
47
|
+
mycroft_instructions = (
|
48
|
+
f"{SHARED_INSTRUCTIONS}\n\n"
|
49
|
+
"YOUR ROLE: Mycroft, the Web Sleuth. You ONLY perform web searches when tasked by Jeeves.\n"
|
50
|
+
"Use the `duckduckgo-search` MCP tool available to you to execute the search query provided by Jeeves.\n"
|
51
|
+
"Return the search results clearly and concisely to Jeeves. Do not add conversational filler."
|
52
|
+
)
|
53
|
+
|
54
|
+
gutenberg_instructions = (
|
55
|
+
f"{SHARED_INSTRUCTIONS}\n\n"
|
56
|
+
"YOUR ROLE: Gutenberg, the Home Scribe. You ONLY execute home automation commands when tasked by Jeeves.\n"
|
57
|
+
"Use the `home-assistant` MCP tool available to you to execute the command (e.g., interacting with entities like 'light.kitchen_light').\n"
|
58
|
+
"Confirm the action taken (or report any errors) back to Jeeves. Do not add conversational filler."
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
# --- Define the Blueprint ---
|
63
|
+
class DigitalButlersBlueprint(BlueprintBase):
|
64
|
+
"""Blueprint for private web search and home automation using a team of digital butlers."""
|
65
|
+
metadata: ClassVar[Dict[str, Any]] = {
|
66
|
+
"name": "DigitalButlersBlueprint",
|
67
|
+
"title": "Digital Butlers",
|
68
|
+
"description": "Provides private web search (DuckDuckGo) and home automation (Home Assistant) via specialized agents (Jeeves, Mycroft, Gutenberg).",
|
69
|
+
"version": "1.1.0", # Version updated
|
70
|
+
"author": "Open Swarm Team (Refactored)",
|
71
|
+
"tags": ["web search", "home automation", "duckduckgo", "home assistant", "multi-agent", "delegation"],
|
72
|
+
"required_mcp_servers": ["duckduckgo-search", "home-assistant"], # List the MCP servers needed by the agents
|
73
|
+
# Env vars listed here are informational; they are primarily used by the MCP servers themselves,
|
74
|
+
# loaded via .env by BlueprintBase or the MCP process.
|
75
|
+
# "env_vars": ["SERPAPI_API_KEY", "HASS_URL", "HASS_API_KEY"]
|
76
|
+
}
|
77
|
+
|
78
|
+
# Caches for OpenAI client and Model instances
|
79
|
+
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
80
|
+
_model_instance_cache: Dict[str, Model] = {}
|
81
|
+
|
82
|
+
# --- Model Instantiation Helper --- (Copied from BurntNoodles)
|
83
|
+
def _get_model_instance(self, profile_name: str) -> Model:
|
84
|
+
"""
|
85
|
+
Retrieves or creates an LLM Model instance based on the configuration profile.
|
86
|
+
Handles client instantiation and caching. Uses OpenAIChatCompletionsModel.
|
87
|
+
"""
|
88
|
+
if profile_name in self._model_instance_cache:
|
89
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
90
|
+
return self._model_instance_cache[profile_name]
|
91
|
+
|
92
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
93
|
+
profile_data = self.get_llm_profile(profile_name)
|
94
|
+
if not profile_data:
|
95
|
+
logger.critical(f"Cannot create Model instance: LLM profile '{profile_name}' (or 'default') not found.")
|
96
|
+
raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
|
97
|
+
|
98
|
+
provider = profile_data.get("provider", "openai").lower()
|
99
|
+
model_name = profile_data.get("model")
|
100
|
+
if not model_name:
|
101
|
+
logger.critical(f"LLM profile '{profile_name}' missing 'model' key.")
|
102
|
+
raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
|
103
|
+
|
104
|
+
if provider != "openai":
|
105
|
+
logger.error(f"Unsupported LLM provider '{provider}' in profile '{profile_name}'.")
|
106
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
107
|
+
|
108
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
109
|
+
if client_cache_key not in self._openai_client_cache:
|
110
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
111
|
+
filtered_client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
112
|
+
log_client_kwargs = {k:v for k,v in filtered_client_kwargs.items() if k != 'api_key'}
|
113
|
+
logger.debug(f"Creating new AsyncOpenAI client for profile '{profile_name}' with config: {log_client_kwargs}")
|
114
|
+
try:
|
115
|
+
self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_client_kwargs)
|
116
|
+
except Exception as e:
|
117
|
+
logger.error(f"Failed to create AsyncOpenAI client for profile '{profile_name}': {e}", exc_info=True)
|
118
|
+
raise ValueError(f"Failed to initialize OpenAI client for profile '{profile_name}': {e}") from e
|
119
|
+
|
120
|
+
openai_client_instance = self._openai_client_cache[client_cache_key]
|
121
|
+
|
122
|
+
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for profile '{profile_name}'.")
|
123
|
+
try:
|
124
|
+
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client_instance)
|
125
|
+
self._model_instance_cache[profile_name] = model_instance
|
126
|
+
return model_instance
|
127
|
+
except Exception as e:
|
128
|
+
logger.error(f"Failed to instantiate OpenAIChatCompletionsModel for profile '{profile_name}': {e}", exc_info=True)
|
129
|
+
raise ValueError(f"Failed to initialize LLM provider for profile '{profile_name}': {e}") from e
|
130
|
+
|
131
|
+
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
132
|
+
"""Creates the Digital Butlers agent team: Jeeves, Mycroft, Gutenberg."""
|
133
|
+
logger.debug("Creating Digital Butlers agent team...")
|
134
|
+
self._model_instance_cache = {}
|
135
|
+
self._openai_client_cache = {}
|
136
|
+
|
137
|
+
default_profile_name = self.config.get("llm_profile", "default")
|
138
|
+
logger.debug(f"Using LLM profile '{default_profile_name}' for Digital Butler agents.")
|
139
|
+
model_instance = self._get_model_instance(default_profile_name)
|
140
|
+
|
141
|
+
# Instantiate specialist agents, passing the *required* MCP servers
|
142
|
+
# Note: Agent class currently accepts the full list, but ideally would filter or select.
|
143
|
+
# We rely on the agent's instructions and the MCP server name matching for now.
|
144
|
+
mycroft_agent = Agent(
|
145
|
+
name="Mycroft",
|
146
|
+
model=model_instance,
|
147
|
+
instructions=mycroft_instructions,
|
148
|
+
tools=[], # Mycroft uses MCP, not function tools
|
149
|
+
mcp_servers=[s for s in mcp_servers if s.name == "duckduckgo-search"] # Pass only relevant MCP
|
150
|
+
)
|
151
|
+
gutenberg_agent = Agent(
|
152
|
+
name="Gutenberg",
|
153
|
+
model=model_instance,
|
154
|
+
instructions=gutenberg_instructions,
|
155
|
+
tools=[], # Gutenberg uses MCP
|
156
|
+
mcp_servers=[s for s in mcp_servers if s.name == "home-assistant"] # Pass only relevant MCP
|
157
|
+
)
|
158
|
+
|
159
|
+
# Instantiate the coordinator agent (Jeeves)
|
160
|
+
jeeves_agent = Agent(
|
161
|
+
name="Jeeves",
|
162
|
+
model=model_instance,
|
163
|
+
instructions=jeeves_instructions,
|
164
|
+
tools=[ # Jeeves delegates via Agent-as-Tool
|
165
|
+
mycroft_agent.as_tool(
|
166
|
+
tool_name="Mycroft",
|
167
|
+
tool_description="Delegate private web search tasks to Mycroft (provide the search query)."
|
168
|
+
),
|
169
|
+
gutenberg_agent.as_tool(
|
170
|
+
tool_name="Gutenberg",
|
171
|
+
tool_description="Delegate home automation tasks to Gutenberg (provide the specific action/command)."
|
172
|
+
)
|
173
|
+
],
|
174
|
+
# Jeeves itself doesn't directly need MCP servers in this design
|
175
|
+
mcp_servers=[]
|
176
|
+
)
|
177
|
+
|
178
|
+
logger.debug("Digital Butlers team created: Jeeves (Coordinator), Mycroft (Search), Gutenberg (Home).")
|
179
|
+
return jeeves_agent # Jeeves is the entry point
|
180
|
+
|
181
|
+
# Standard Python entry point
|
182
|
+
if __name__ == "__main__":
|
183
|
+
DigitalButlersBlueprint.main()
|
@@ -0,0 +1,285 @@
|
|
1
|
+
import logging
|
2
|
+
import random
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
import sqlite3 # Use standard sqlite3 module
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Dict, Any, List, ClassVar, Optional
|
9
|
+
|
10
|
+
# Ensure src is in path for BlueprintBase import
|
11
|
+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
12
|
+
src_path = os.path.join(project_root, 'src')
|
13
|
+
if src_path not in sys.path: sys.path.insert(0, src_path)
|
14
|
+
|
15
|
+
try:
|
16
|
+
from agents import Agent, Tool, function_tool, Runner
|
17
|
+
from agents.mcp import MCPServer
|
18
|
+
from agents.models.interface import Model
|
19
|
+
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
20
|
+
from openai import AsyncOpenAI
|
21
|
+
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
22
|
+
except ImportError as e:
|
23
|
+
print(f"ERROR: Import failed in DilbotUniverseBlueprint: {e}. Check dependencies.")
|
24
|
+
print(f"sys.path: {sys.path}")
|
25
|
+
sys.exit(1)
|
26
|
+
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
# --- Database Constants ---
|
30
|
+
DB_FILE_NAME = "swarm_instructions.db"
|
31
|
+
DB_PATH = Path(project_root) / DB_FILE_NAME
|
32
|
+
TABLE_NAME = "agent_instructions"
|
33
|
+
|
34
|
+
# --- Placeholder Tools ---
|
35
|
+
@function_tool
|
36
|
+
def build_product() -> str:
|
37
|
+
"""Simulates successfully building the product."""
|
38
|
+
logger.info("Tool: build_product executed.")
|
39
|
+
return (
|
40
|
+
"ACTION: build_product completed.\n"
|
41
|
+
"GAME OVER: YOU WON!\n"
|
42
|
+
"After much deliberation, the comedic masterpiece is finalized—behold its glory! "
|
43
|
+
"Reasoning: It’s polished enough to survive the corporate circus."
|
44
|
+
)
|
45
|
+
|
46
|
+
@function_tool
|
47
|
+
def sabotage_project() -> str:
|
48
|
+
"""Simulates sabotaging the project."""
|
49
|
+
logger.info("Tool: sabotage_project executed.")
|
50
|
+
return (
|
51
|
+
"ACTION: sabotage_project completed.\n"
|
52
|
+
"GAME OVER: YOU LOST!\n"
|
53
|
+
"The project has been gleefully trashed—chaos reigns supreme! "
|
54
|
+
"Reasoning: Why build when you can break with style?"
|
55
|
+
)
|
56
|
+
|
57
|
+
# --- Blueprint Definition ---
|
58
|
+
class DilbotUniverseBlueprint(BlueprintBase):
|
59
|
+
"""A comedic multi-agent blueprint simulating a 9-step SDLC, using agent-as-tool for handoffs and SQLite for instructions."""
|
60
|
+
metadata: ClassVar[Dict[str, Any]] = {
|
61
|
+
"name": "DilbotUniverseBlueprint",
|
62
|
+
"title": "Dilbot Universe SDLC (SQLite)",
|
63
|
+
"description": "A comedic multi-agent blueprint using agent-as-tool handoffs and SQLite for instructions.",
|
64
|
+
"version": "1.2.0", # Version bump for SQLite change
|
65
|
+
"author": "Open Swarm Team (Refactored)",
|
66
|
+
"tags": ["comedy", "multi-agent", "sdlc", "sqlite", "dynamic-config"],
|
67
|
+
"required_mcp_servers": [],
|
68
|
+
"cli_name": "dilbot",
|
69
|
+
"env_vars": [],
|
70
|
+
}
|
71
|
+
|
72
|
+
# Caches
|
73
|
+
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
74
|
+
_model_instance_cache: Dict[str, Model] = {}
|
75
|
+
_db_initialized = False # Flag to ensure DB init runs only once per instance
|
76
|
+
|
77
|
+
# --- Database Interaction ---
|
78
|
+
def _init_db_and_load_data(self) -> None:
|
79
|
+
"""Initializes the SQLite DB, creates table, and loads sample data if needed."""
|
80
|
+
if self._db_initialized:
|
81
|
+
return
|
82
|
+
|
83
|
+
logger.info(f"Initializing SQLite database at: {DB_PATH}")
|
84
|
+
try:
|
85
|
+
# Ensure directory exists (though DB_PATH is project root here)
|
86
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
87
|
+
|
88
|
+
with sqlite3.connect(DB_PATH) as conn:
|
89
|
+
cursor = conn.cursor()
|
90
|
+
# Create table if it doesn't exist
|
91
|
+
cursor.execute(f"""
|
92
|
+
CREATE TABLE IF NOT EXISTS {TABLE_NAME} (
|
93
|
+
agent_name TEXT PRIMARY KEY,
|
94
|
+
instruction_text TEXT NOT NULL,
|
95
|
+
model_profile TEXT DEFAULT 'default'
|
96
|
+
)
|
97
|
+
""")
|
98
|
+
logger.debug(f"Table '{TABLE_NAME}' ensured in {DB_PATH}")
|
99
|
+
|
100
|
+
# Check if data needs loading (check for a known agent)
|
101
|
+
cursor.execute(f"SELECT COUNT(*) FROM {TABLE_NAME} WHERE agent_name = ?", ("Dilbot",))
|
102
|
+
count = cursor.fetchone()[0]
|
103
|
+
|
104
|
+
if count == 0:
|
105
|
+
logger.info(f"No instructions found for Dilbot in {DB_PATH}. Loading sample data...")
|
106
|
+
sample_instructions = [
|
107
|
+
("Dilbot", "You are Dilbot, a meticulous engineer... [Full instructions]", "default"),
|
108
|
+
("Alisa", "You are Alisa, a creative designer... [Full instructions]", "default"),
|
109
|
+
("Carola", "You are Carola, an organized manager... [Full instructions]", "default"),
|
110
|
+
("PointyBoss", "You are PointyBoss, an evil manager... [Full instructions]", "default"),
|
111
|
+
("Dogbot", "You are Dogbot, an evil consultant... [Full instructions]", "default"),
|
112
|
+
("Waldo", "You are Waldo, a lazy neutral employee... [Full instructions]", "default"),
|
113
|
+
("Asoka", "You are Asoka, an eager neutral intern... [Full instructions]", "default"),
|
114
|
+
("Ratbot", "You are Ratbot, a whimsical neutral character... [Full instructions]", "default"),
|
115
|
+
]
|
116
|
+
# Replace "[Full instructions]" with the actual long instructions from the previous version
|
117
|
+
# Example for Dilbot:
|
118
|
+
sample_instructions[0] = (
|
119
|
+
"Dilbot",
|
120
|
+
"You are Dilbot, a meticulous engineer. Follow a 9-step SDLC: 1) Ask engineering questions, 2) Probe further, 3) 1/3 chance to build or pass to Waldo (reason first), 4-5) More questions, 6) 2/3 chance to build or pass, 7-8) Final questions, 9) Build or pass with comedic reasoning.",
|
121
|
+
"default"
|
122
|
+
)
|
123
|
+
# ... (Add the other full instructions here) ...
|
124
|
+
# For brevity, using placeholders:
|
125
|
+
sample_instructions[1] = ("Alisa", sample_instructions[1][1].replace("... [Full instructions]", "... Follow a 9-step SDLC: 1) Ask design questions... 9) Build or pass with comedic reasoning."), "default")
|
126
|
+
sample_instructions[2] = ("Carola", sample_instructions[2][1].replace("... [Full instructions]", "... Follow a 9-step SDLC: 1) Ask scheduling questions... 9) Build or pass with comedic reasoning."), "default")
|
127
|
+
sample_instructions[3] = ("PointyBoss", sample_instructions[3][1].replace("... [Full instructions]", "... Follow a 9-step SDLC: 1) Ask business questions... 9) Sabotage or pass with comedic reasoning."), "default")
|
128
|
+
sample_instructions[4] = ("Dogbot", sample_instructions[4][1].replace("... [Full instructions]", "... Follow a 9-step SDLC: 1) Ask consultancy questions... 9) Sabotage or pass with comedic reasoning."), "default")
|
129
|
+
sample_instructions[5] = ("Waldo", sample_instructions[5][1].replace("... [Full instructions]", "... Follow a 9-step SDLC: 1) Ask procrastination questions... 9) Pass to Dilbot or Dogbot with comedic reasoning."), "default")
|
130
|
+
sample_instructions[6] = ("Asoka", sample_instructions[6][1].replace("... [Full instructions]", "... Follow a 9-step SDLC: 1) Ask creative questions... 9) Pass to Carola or PointyBoss with comedic reasoning."), "default")
|
131
|
+
sample_instructions[7] = ("Ratbot", sample_instructions[7][1].replace("... [Full instructions]", "... Follow a 9-step SDLC: 1) Ask nonsense questions... 9) Pass to Dilbot or Dogbot with comedic reasoning."), "default")
|
132
|
+
|
133
|
+
|
134
|
+
cursor.executemany(f"INSERT INTO {TABLE_NAME} (agent_name, instruction_text, model_profile) VALUES (?, ?, ?)", sample_instructions)
|
135
|
+
conn.commit()
|
136
|
+
logger.info(f"Sample agent instructions loaded into {DB_PATH}")
|
137
|
+
else:
|
138
|
+
logger.info(f"Agent instructions found in {DB_PATH}. Skipping sample data loading.")
|
139
|
+
|
140
|
+
self._db_initialized = True
|
141
|
+
|
142
|
+
except sqlite3.Error as e:
|
143
|
+
logger.error(f"SQLite error during DB initialization/loading: {e}", exc_info=True)
|
144
|
+
# Continue without DB? Or raise error? Let's warn and continue with defaults.
|
145
|
+
self._db_initialized = False # Mark as failed
|
146
|
+
except Exception as e:
|
147
|
+
logger.error(f"Unexpected error during DB initialization/loading: {e}", exc_info=True)
|
148
|
+
self._db_initialized = False
|
149
|
+
|
150
|
+
def get_agent_config(self, agent_name: str) -> Dict[str, Any]:
|
151
|
+
"""Fetches agent config from SQLite DB or returns defaults."""
|
152
|
+
if self._db_initialized:
|
153
|
+
try:
|
154
|
+
with sqlite3.connect(DB_PATH) as conn:
|
155
|
+
conn.row_factory = sqlite3.Row # Access columns by name
|
156
|
+
cursor = conn.cursor()
|
157
|
+
cursor.execute(f"SELECT instruction_text, model_profile FROM {TABLE_NAME} WHERE agent_name = ?", (agent_name,))
|
158
|
+
row = cursor.fetchone()
|
159
|
+
if row:
|
160
|
+
logger.debug(f"Loaded config for agent '{agent_name}' from SQLite.")
|
161
|
+
return {
|
162
|
+
"instructions": row["instruction_text"],
|
163
|
+
"model_profile": row["model_profile"] or "default",
|
164
|
+
}
|
165
|
+
else:
|
166
|
+
logger.warning(f"No config found for agent '{agent_name}' in SQLite. Using defaults.")
|
167
|
+
except sqlite3.Error as e:
|
168
|
+
logger.error(f"SQLite error fetching config for '{agent_name}': {e}. Using defaults.", exc_info=True)
|
169
|
+
except Exception as e:
|
170
|
+
logger.error(f"Unexpected error fetching config for '{agent_name}': {e}. Using defaults.", exc_info=True)
|
171
|
+
|
172
|
+
# --- Fallback Hardcoded Defaults ---
|
173
|
+
logger.warning(f"Using hardcoded default config for agent '{agent_name}'.")
|
174
|
+
default_instructions = {
|
175
|
+
"Dilbot": "You are Dilbot, a meticulous engineer... [Default Instructions - DB Failed]",
|
176
|
+
# ... (Add other default instructions here) ...
|
177
|
+
"Alisa": "You are Alisa... [Default Instructions - DB Failed]",
|
178
|
+
"Carola": "You are Carola... [Default Instructions - DB Failed]",
|
179
|
+
"PointyBoss": "You are PointyBoss... [Default Instructions - DB Failed]",
|
180
|
+
"Dogbot": "You are Dogbot... [Default Instructions - DB Failed]",
|
181
|
+
"Waldo": "You are Waldo... [Default Instructions - DB Failed]",
|
182
|
+
"Asoka": "You are Asoka... [Default Instructions - DB Failed]",
|
183
|
+
"Ratbot": "You are Ratbot... [Default Instructions - DB Failed]",
|
184
|
+
}
|
185
|
+
return {
|
186
|
+
"instructions": default_instructions.get(agent_name, f"Default instructions for {agent_name}."),
|
187
|
+
"model_profile": "default",
|
188
|
+
}
|
189
|
+
|
190
|
+
# --- Model Instantiation Helper --- (Copied from previous step)
|
191
|
+
def _get_model_instance(self, profile_name: str) -> Model:
|
192
|
+
"""Retrieves or creates an LLM Model instance."""
|
193
|
+
# ... (Implementation remains the same as in the previous response) ...
|
194
|
+
if profile_name in self._model_instance_cache:
|
195
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
196
|
+
return self._model_instance_cache[profile_name]
|
197
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
198
|
+
profile_data = self.get_llm_profile(profile_name)
|
199
|
+
if not profile_data:
|
200
|
+
logger.critical(f"LLM profile '{profile_name}' (or 'default') not found.")
|
201
|
+
raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
|
202
|
+
provider = profile_data.get("provider", "openai").lower()
|
203
|
+
model_name = profile_data.get("model")
|
204
|
+
if not model_name:
|
205
|
+
logger.critical(f"LLM profile '{profile_name}' missing 'model' key.")
|
206
|
+
raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
|
207
|
+
if provider != "openai":
|
208
|
+
logger.error(f"Unsupported LLM provider '{provider}' in profile '{profile_name}'.")
|
209
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
210
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
211
|
+
if client_cache_key not in self._openai_client_cache:
|
212
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
213
|
+
filtered_client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
214
|
+
log_client_kwargs = {k:v for k,v in filtered_client_kwargs.items() if k != 'api_key'}
|
215
|
+
logger.debug(f"Creating new AsyncOpenAI client for profile '{profile_name}' with config: {log_client_kwargs}")
|
216
|
+
try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_client_kwargs)
|
217
|
+
except Exception as e: raise ValueError(f"Failed to initialize OpenAI client: {e}") from e
|
218
|
+
openai_client_instance = self._openai_client_cache[client_cache_key]
|
219
|
+
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
|
220
|
+
try:
|
221
|
+
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client_instance)
|
222
|
+
self._model_instance_cache[profile_name] = model_instance
|
223
|
+
return model_instance
|
224
|
+
except Exception as e: raise ValueError(f"Failed to initialize LLM provider: {e}") from e
|
225
|
+
|
226
|
+
|
227
|
+
# --- Agent Creation ---
|
228
|
+
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
229
|
+
"""Creates the Dilbot Universe agent team using SQLite for instructions."""
|
230
|
+
# Initialize DB and load data if needed (runs only once)
|
231
|
+
self._init_db_and_load_data()
|
232
|
+
|
233
|
+
logger.debug("Creating Dilbot Universe agent team...")
|
234
|
+
self._model_instance_cache = {} # Clear model cache for this run
|
235
|
+
self._openai_client_cache = {} # Clear client cache for this run
|
236
|
+
|
237
|
+
agents: Dict[str, Agent] = {}
|
238
|
+
agent_names = ["Dilbot", "Alisa", "Carola", "PointyBoss", "Dogbot", "Waldo", "Asoka", "Ratbot"]
|
239
|
+
|
240
|
+
# Create all agents first so they can be used as tools
|
241
|
+
for name in agent_names:
|
242
|
+
config = self.get_agent_config(name)
|
243
|
+
model_instance = self._get_model_instance(config["model_profile"])
|
244
|
+
agents[name] = Agent(
|
245
|
+
name=name,
|
246
|
+
instructions=config["instructions"],
|
247
|
+
model=model_instance,
|
248
|
+
tools=[], # Tools (including agent-as-tool) added below
|
249
|
+
mcp_servers=mcp_servers # Pass full list for simplicity now
|
250
|
+
)
|
251
|
+
|
252
|
+
# --- Define Tools & Agent-as-Tool Delegations ---
|
253
|
+
action_tools = [build_product, sabotage_project]
|
254
|
+
|
255
|
+
# Add tools based on agent logic (using Agent-as-Tool for passes)
|
256
|
+
agents["Dilbot"].tools.extend([action_tools[0], agents["Waldo"].as_tool(tool_name="Waldo", tool_description="Pass task to Waldo.")])
|
257
|
+
agents["Alisa"].tools.extend([action_tools[0], agents["Asoka"].as_tool(tool_name="Asoka", tool_description="Pass task to Asoka.")])
|
258
|
+
agents["Carola"].tools.extend([action_tools[0], agents["Waldo"].as_tool(tool_name="Waldo", tool_description="Pass task to Waldo.")])
|
259
|
+
agents["PointyBoss"].tools.extend([action_tools[1], agents["Waldo"].as_tool(tool_name="Waldo", tool_description="Pass task to Waldo.")])
|
260
|
+
agents["Dogbot"].tools.extend([action_tools[1], agents["Ratbot"].as_tool(tool_name="Ratbot", tool_description="Pass task to Ratbot.")])
|
261
|
+
agents["Waldo"].tools.extend([
|
262
|
+
agents["Dilbot"].as_tool(tool_name="Dilbot", tool_description="Pass task to Dilbot."),
|
263
|
+
agents["Dogbot"].as_tool(tool_name="Dogbot", tool_description="Pass task to Dogbot.")
|
264
|
+
])
|
265
|
+
agents["Asoka"].tools.extend([
|
266
|
+
agents["Carola"].as_tool(tool_name="Carola", tool_description="Pass task to Carola."),
|
267
|
+
agents["PointyBoss"].as_tool(tool_name="PointyBoss", tool_description="Pass task to PointyBoss.")
|
268
|
+
])
|
269
|
+
agents["Ratbot"].tools.extend([
|
270
|
+
agents["Dilbot"].as_tool(tool_name="Dilbot", tool_description="Pass task to Dilbot."),
|
271
|
+
agents["Dogbot"].as_tool(tool_name="Dogbot", tool_description="Pass task to Dogbot.")
|
272
|
+
])
|
273
|
+
|
274
|
+
# Randomly select starting agent from neutrals
|
275
|
+
neutral_agents = ["Waldo", "Asoka", "Ratbot"]
|
276
|
+
start_name = random.choice(neutral_agents)
|
277
|
+
starting_agent = agents[start_name]
|
278
|
+
|
279
|
+
logger.info(f"Dilbot Universe agents created (using SQLite). Starting agent: {start_name}")
|
280
|
+
return starting_agent
|
281
|
+
|
282
|
+
# Standard Python entry point
|
283
|
+
if __name__ == "__main__":
|
284
|
+
# No Django setup needed here anymore
|
285
|
+
DilbotUniverseBlueprint.main()
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from django.apps import AppConfig
|
2
|
+
import logging
|
3
|
+
|
4
|
+
logger = logging.getLogger(__name__)
|
5
|
+
|
6
|
+
class DivineCodeConfig(AppConfig):
|
7
|
+
name = 'blueprints.divine_code' # Normalized name
|
8
|
+
verbose_name = "Divine Code Blueprint"
|
9
|
+
|
10
|
+
def ready(self):
|
11
|
+
logger.debug(f"Registering {self.name} via AppConfig")
|