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
swarm/auth.py
CHANGED
@@ -1,56 +1,60 @@
|
|
1
|
-
import os
|
2
1
|
import logging
|
2
|
+
import os
|
3
|
+
from rest_framework.authentication import BaseAuthentication, SessionAuthentication
|
4
|
+
from rest_framework import exceptions
|
5
|
+
from django.conf import settings
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
7
|
+
# Import AnonymousUser
|
3
8
|
from django.contrib.auth.models import AnonymousUser
|
4
|
-
|
5
|
-
from
|
6
|
-
|
7
|
-
logger = logging.getLogger(__name__)
|
9
|
+
# Keep get_user_model if CustomSessionAuthentication needs it or for future user mapping
|
10
|
+
from django.contrib.auth import get_user_model
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
@property
|
12
|
-
def is_authenticated(self) -> bool: # type: ignore[override]
|
13
|
-
return True # Ensure Django recognizes this user as authenticated
|
12
|
+
logger = logging.getLogger('swarm.auth')
|
13
|
+
User = get_user_model()
|
14
14
|
|
15
|
-
|
15
|
+
# --- Static Token Authentication ---
|
16
|
+
class StaticTokenAuthentication(BaseAuthentication):
|
16
17
|
"""
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
3. Otherwise, falls back to Django's TokenAuthentication.
|
18
|
+
Authenticates requests based on a static API token passed in a header.
|
19
|
+
Returns (AnonymousUser, token) on success to satisfy DRF's expectations
|
20
|
+
while signaling that a specific user isn't associated.
|
21
21
|
"""
|
22
|
+
keyword = 'Bearer'
|
23
|
+
|
22
24
|
def authenticate(self, request):
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
25
|
+
logger.debug("[Auth][StaticToken] StaticTokenAuthentication.authenticate called.")
|
26
|
+
expected_token = getattr(settings, 'SWARM_API_KEY', None)
|
27
|
+
|
28
|
+
if not expected_token:
|
29
|
+
logger.error("[Auth][StaticToken] SWARM_API_KEY is not set in Django settings. Cannot authenticate.")
|
30
|
+
return None
|
31
|
+
|
32
|
+
provided_token = None
|
33
|
+
auth_header = request.META.get('HTTP_AUTHORIZATION', '').split()
|
34
|
+
if len(auth_header) == 2 and auth_header[0].lower() == self.keyword.lower():
|
35
|
+
provided_token = auth_header[1]
|
36
|
+
logger.debug(f"[Auth][StaticToken] Found token in Authorization header: {provided_token[:6]}...")
|
37
|
+
else:
|
38
|
+
provided_token = request.META.get('HTTP_X_API_KEY')
|
39
|
+
if provided_token:
|
40
|
+
logger.debug(f"[Auth][StaticToken] Found token in X-API-Key header: {provided_token[:6]}...")
|
41
|
+
|
42
|
+
if not provided_token:
|
43
|
+
logger.debug("[Auth][StaticToken] No token found in headers.")
|
44
|
+
return None
|
45
|
+
|
46
|
+
# Use constant time comparison if possible in future?
|
47
|
+
if provided_token == expected_token:
|
48
|
+
logger.info("[Auth][StaticToken] Static token authentication successful.")
|
49
|
+
# *** Return AnonymousUser and the token ***
|
50
|
+
# This sets request.user to AnonymousUser and request.auth to the token.
|
51
|
+
return (AnonymousUser(), provided_token)
|
52
|
+
else:
|
53
|
+
logger.warning(f"[Auth][StaticToken] Invalid token provided: {provided_token[:6]}...")
|
54
|
+
raise exceptions.AuthenticationFailed(_("Invalid API Key."))
|
55
|
+
|
56
|
+
# --- Custom *Synchronous* Session Authentication ---
|
57
|
+
class CustomSessionAuthentication(SessionAuthentication):
|
58
|
+
""" Standard SessionAuthentication """
|
59
|
+
pass
|
60
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Blueprints Overview
|
2
|
+
|
3
|
+
This directory contains example blueprints for the Open Swarm framework, showcasing agent coordination, external data handling, database operations, and more via parody-themed agent teams. Each blueprint achieves a practical outcome while demonstrating specific framework capabilities. Blueprints are generally ordered by complexity.
|
4
|
+
|
5
|
+
## Refactored Blueprints (Using `BlueprintBase`)
|
6
|
+
|
7
|
+
These blueprints have been updated to use the `BlueprintBase` class, `openai-agents` library conventions (like `Agent`, `@function_tool`, agent-as-tool delegation), and standardized configuration loading.
|
8
|
+
|
9
|
+
| Blueprint Name | CLI (`uv run ...`) Example Instruction | What it Demonstrates | Key Features | MCP Servers Used (Examples) |
|
10
|
+
|---------------------------------|-------------------------------------------------------|--------------------------------------------------------------------------------|---------------------------------------------------------------------------|-----------------------------|
|
11
|
+
| **EchoCraft** | `--instruction "Repeat this message"` | Simplest blueprint, direct input echo | Basic `BlueprintBase` structure, Agent `process` override | None |
|
12
|
+
| **Suggestion** | `--instruction "Topic: AI Ethics"` | Generating structured JSON output | Agent `output_type=TypedDict`, JSON mode | None |
|
13
|
+
| **Chatbot** | `--instruction "Tell me a joke"` | Basic single-agent conversation | Standard `Agent` interaction with LLM | None |
|
14
|
+
| **BurntNoodles** | `--instruction "Check git status"` | Coordinating Git & testing tasks via function tools & agent delegation | `@function_tool` for CLI commands, Agent-as-tool delegation | None |
|
15
|
+
| **RueCode** | `--instruction "Refactor this python code..."` | Multi-agent code generation/refactoring workflow | Agent-as-tool delegation, specialized agent roles (Coordinator, Code, etc.) | memory |
|
16
|
+
| **NebulaShellzzar** | `--instruction "List files in /tmp"` | Matrix-themed sysadmin/coding tasks with delegation | Agent-as-tool delegation, `@function_tool` for shell/code analysis | memory |
|
17
|
+
| **DigitalButlers** | `--instruction "Search for nearby restaurants"` | Delegating tasks requiring specific MCPs (search, home automation) | Agent-as-tool delegation, MCP usage by specialist agents | duckduckgo-search, home-assistant |
|
18
|
+
| **DilbotUniverse (SQLite)** | `--instruction "Start the SDLC"` | Comedic SDLC simulation, instructions loaded from SQLite | Agent-as-tool delegation, SQLite integration for dynamic prompts | sqlite |
|
19
|
+
| **FamilyTies** | `--instruction "Create WP post titled 'Hello'..."` | Coordinating WordPress operations via MCP | Agent-as-tool delegation, specialized agent using specific MCP (WP) | server-wp-mcp |
|
20
|
+
| **MissionImprobable (SQLite)** | `--instruction "Use RollinFumble to run 'pwd'"` | Spy-themed ops, instructions from SQLite, multi-level delegation | Agent-as-tool delegation, SQLite integration, MCP usage (fs, shell, mem) | memory, filesystem, mcp-shell |
|
21
|
+
| **WhiskeyTangoFoxtrot** | `--instruction "Find free vector DBs"` | Hierarchical agents tracking services using DB & web search | Multi-level agent delegation, SQLite, various search/scrape/doc MCPs | sqlite, brave-search, mcp-npx-fetch, mcp-doc-forge, filesystem |
|
22
|
+
| **DivineOps** | `--instruction "Design user auth API"` | Large-scale SW dev coordination (Design, Implement, DB, DevOps, Docs) | Complex delegation, wide range of MCP usage (search, shell, db, fs...) | memory, filesystem, mcp-shell, sqlite, sequential-thinking, brave-search |
|
23
|
+
| **Gaggle** | `--instruction "Write story: cat library"` | Collaborative story writing (Planner, Writer, Editor) | Agent-as-tool delegation, function tools for writing steps | None |
|
24
|
+
| **MonkaiMagic** | `--instruction "List AWS S3 buckets"` | Cloud operations (AWS, Fly, Vercel) via direct CLI function tools | `@function_tool` for external CLIs, agent-as-tool delegation | mcp-shell (for Sandy) |
|
25
|
+
| **UnapologeticPress (SQLite)** | `--instruction "Write poem: city rain"` | Collaborative poetry writing by distinct "poet" agents, SQLite instructions | Agent-as-tool (all-to-all), SQLite, broad MCP usage | Various (see blueprint) |
|
26
|
+
| **Omniplex** | `--instruction "Use filesystem to read README.md"` | Dynamically routes tasks based on MCP server type (npx, uvx, other) | Dynamic agent/tool creation based on available MCPs | Dynamic (all available) |
|
27
|
+
|
28
|
+
## WIP / Needs Refactoring
|
29
|
+
|
30
|
+
These blueprints still use older patterns or have known issues (e.g., UVX/NeMo dependencies) and need refactoring to the `BlueprintBase` standard.
|
31
|
+
|
32
|
+
| Blueprint Name | CLI | Description | Status |
|
33
|
+
|-------------------------|----------|--------------------------------------------------------------|-----------------|
|
34
|
+
| chucks_angels | chuck | Manages transcripts, compute, Flowise (UVX/NeMo WIP) | Needs Refactor |
|
35
|
+
| django_chat | djchat | Django-integrated chatbot example | Needs Review |
|
36
|
+
| flock | flock | (Details TBC) | Needs Refactor |
|
37
|
+
| messenger | msg | (Details TBC) | Needs Refactor |
|
38
|
+
|
39
|
+
## Configuration (`swarm_config.json`)
|
40
|
+
|
41
|
+
The framework uses a central `swarm_config.json` file (usually in the project root) to define:
|
42
|
+
|
43
|
+
* **`llm`**: Profiles for different language models (provider, model name, API keys via `${ENV_VAR}`, base URL, etc.).
|
44
|
+
* **`mcpServers`**: Definitions for starting external MCP servers. Each entry includes:
|
45
|
+
* `command`: The command to run (e.g., `npx`, `uvx`, `python`, `docker`). Can be a string or list.
|
46
|
+
* `args`: A list of arguments for the command.
|
47
|
+
* `env`: A dictionary of environment variables to set for the server process.
|
48
|
+
* `cwd`: (Optional) Working directory for the server process.
|
49
|
+
* `description`: (Optional) A human-readable description of the server's function.
|
50
|
+
* `startup_timeout`: (Optional) Seconds to wait for the server to start and connect (default: 30).
|
51
|
+
* **`blueprints`**: Optional section for blueprint-specific overrides (e.g., default profile, max calls).
|
52
|
+
* **`defaults`**: Global default settings (e.g., `default_markdown_cli`).
|
53
|
+
|
54
|
+
## Environment Variables
|
55
|
+
|
56
|
+
Many blueprints or their required MCP servers depend on environment variables (e.g., API keys). These should ideally be set in a `.env` file in the project root. `BlueprintBase` will automatically load this file. See individual blueprint metadata (`env_vars`) or `swarm_config.json` for potentially required variables. The `BlueprintBase` will warn if variables listed in a blueprint's `metadata["env_vars"]` are not set.
|
57
|
+
|
58
|
+
## Running Blueprints (Development)
|
59
|
+
|
60
|
+
Use `uv run python <path_to_blueprint.py> --instruction "Your instruction"`
|
61
|
+
|
62
|
+
Common flags:
|
63
|
+
* `--debug`: Enable detailed DEBUG logging.
|
64
|
+
* `--quiet`: Suppress most logs, print only final output.
|
65
|
+
* `--config-path`: Specify a different config file location.
|
66
|
+
* `--profile`: Use a specific LLM profile from the config.
|
67
|
+
* `--markdown` / `--no-markdown`: Force markdown rendering on/off.
|
@@ -0,0 +1,412 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
import asyncio
|
5
|
+
import subprocess
|
6
|
+
import shlex # Added for safe command splitting
|
7
|
+
import re
|
8
|
+
import inspect
|
9
|
+
from pathlib import Path # Use pathlib for better path handling
|
10
|
+
from typing import Dict, Any, List, Optional, ClassVar
|
11
|
+
|
12
|
+
try:
|
13
|
+
# Core imports from openai-agents
|
14
|
+
from agents import Agent, Tool, function_tool, Runner
|
15
|
+
from agents.mcp import MCPServer
|
16
|
+
from agents.models.interface import Model
|
17
|
+
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
18
|
+
from openai import AsyncOpenAI
|
19
|
+
|
20
|
+
# Import our custom base class
|
21
|
+
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
22
|
+
except ImportError as e:
|
23
|
+
# Provide more helpful error message
|
24
|
+
print(f"ERROR: Import failed in BurntNoodlesBlueprint: {e}. Check 'openai-agents' install and project structure.")
|
25
|
+
print(f"Attempted import from directory: {os.path.dirname(__file__)}")
|
26
|
+
print(f"sys.path: {sys.path}")
|
27
|
+
sys.exit(1)
|
28
|
+
|
29
|
+
# Configure logging for this blueprint module
|
30
|
+
logger = logging.getLogger(__name__)
|
31
|
+
# Logging level is controlled by BlueprintBase based on --debug flag
|
32
|
+
|
33
|
+
# --- Tool Definitions ---
|
34
|
+
# Standalone functions decorated as tools for git and testing operations.
|
35
|
+
# Enhanced error handling and logging added.
|
36
|
+
|
37
|
+
@function_tool
|
38
|
+
def git_status() -> str:
|
39
|
+
"""Executes 'git status --porcelain' and returns the current repository status."""
|
40
|
+
logger.info("Executing git status --porcelain") # Keep INFO for tool execution start
|
41
|
+
try:
|
42
|
+
# Using --porcelain for machine-readable output
|
43
|
+
result = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True, check=True, timeout=30)
|
44
|
+
output = result.stdout.strip()
|
45
|
+
logger.debug(f"Git status raw output:\n{output}")
|
46
|
+
return f"OK: Git Status:\n{output}" if output else "OK: No changes detected in the working directory."
|
47
|
+
except FileNotFoundError:
|
48
|
+
logger.error("Git command not found. Is git installed and in PATH?")
|
49
|
+
return "Error: git command not found."
|
50
|
+
except subprocess.CalledProcessError as e:
|
51
|
+
logger.error(f"Error executing git status: {e.stderr}")
|
52
|
+
return f"Error executing git status: {e.stderr}"
|
53
|
+
except subprocess.TimeoutExpired:
|
54
|
+
logger.error("Git status command timed out.")
|
55
|
+
return "Error: Git status command timed out."
|
56
|
+
except Exception as e:
|
57
|
+
logger.error(f"Unexpected error during git status: {e}", exc_info=logger.level <= logging.DEBUG)
|
58
|
+
return f"Error during git status: {e}"
|
59
|
+
|
60
|
+
@function_tool
|
61
|
+
def git_diff() -> str:
|
62
|
+
"""Executes 'git diff' and returns the differences in the working directory."""
|
63
|
+
logger.info("Executing git diff") # Keep INFO for tool execution start
|
64
|
+
try:
|
65
|
+
result = subprocess.run(["git", "diff"], capture_output=True, text=True, check=False, timeout=30) # Use check=False, handle exit code
|
66
|
+
output = result.stdout
|
67
|
+
stderr = result.stderr.strip()
|
68
|
+
if result.returncode != 0 and stderr: # Error occurred
|
69
|
+
logger.error(f"Error executing git diff (Exit Code {result.returncode}): {stderr}")
|
70
|
+
return f"Error executing git diff: {stderr}"
|
71
|
+
logger.debug(f"Git diff raw output (Exit Code {result.returncode}):\n{output[:1000]}...") # Log snippet
|
72
|
+
return f"OK: Git Diff Output:\n{output}" if output else "OK: No differences found."
|
73
|
+
except FileNotFoundError:
|
74
|
+
logger.error("Git command not found.")
|
75
|
+
return "Error: git command not found."
|
76
|
+
except subprocess.TimeoutExpired:
|
77
|
+
logger.error("Git diff command timed out.")
|
78
|
+
return "Error: Git diff command timed out."
|
79
|
+
except Exception as e:
|
80
|
+
logger.error(f"Unexpected error during git diff: {e}", exc_info=logger.level <= logging.DEBUG)
|
81
|
+
return f"Error during git diff: {e}"
|
82
|
+
|
83
|
+
@function_tool
|
84
|
+
def git_add(file_path: str = ".") -> str:
|
85
|
+
"""Executes 'git add' to stage changes for the specified file or all changes (default '.')."""
|
86
|
+
logger.info(f"Executing git add {file_path}") # Keep INFO for tool execution start
|
87
|
+
try:
|
88
|
+
result = subprocess.run(["git", "add", file_path], capture_output=True, text=True, check=True, timeout=30)
|
89
|
+
logger.debug(f"Git add '{file_path}' completed successfully.")
|
90
|
+
return f"OK: Staged '{file_path}' successfully."
|
91
|
+
except FileNotFoundError:
|
92
|
+
logger.error("Git command not found.")
|
93
|
+
return "Error: git command not found."
|
94
|
+
except subprocess.CalledProcessError as e:
|
95
|
+
logger.error(f"Error executing git add '{file_path}': {e.stderr}")
|
96
|
+
return f"Error executing git add '{file_path}': {e.stderr}"
|
97
|
+
except subprocess.TimeoutExpired:
|
98
|
+
logger.error(f"Git add command timed out for '{file_path}'.")
|
99
|
+
return f"Error: Git add command timed out for '{file_path}'."
|
100
|
+
except Exception as e:
|
101
|
+
logger.error(f"Unexpected error during git add '{file_path}': {e}", exc_info=logger.level <= logging.DEBUG)
|
102
|
+
return f"Error during git add '{file_path}': {e}"
|
103
|
+
|
104
|
+
@function_tool
|
105
|
+
def git_commit(message: str) -> str:
|
106
|
+
"""Executes 'git commit' with a provided commit message."""
|
107
|
+
logger.info(f"Executing git commit -m '{message[:50]}...'") # Keep INFO for tool execution start
|
108
|
+
if not message or not message.strip():
|
109
|
+
logger.warning("Git commit attempted with empty or whitespace-only message.")
|
110
|
+
return "Error: Commit message cannot be empty."
|
111
|
+
try:
|
112
|
+
# Using list form is generally safer than shell=True for complex args
|
113
|
+
result = subprocess.run(["git", "commit", "-m", message], capture_output=True, text=True, check=False, timeout=30) # Use check=False
|
114
|
+
output = result.stdout.strip()
|
115
|
+
stderr = result.stderr.strip()
|
116
|
+
logger.debug(f"Git commit raw output (Exit Code {result.returncode}):\nSTDOUT: {output}\nSTDERR: {stderr}")
|
117
|
+
|
118
|
+
# Handle common non-error cases explicitly
|
119
|
+
if "nothing to commit" in output or "nothing added to commit" in output or "no changes added to commit" in output:
|
120
|
+
logger.info("Git commit reported: Nothing to commit.")
|
121
|
+
return "OK: Nothing to commit."
|
122
|
+
if result.returncode == 0:
|
123
|
+
return f"OK: Committed with message '{message}'.\n{output}"
|
124
|
+
else:
|
125
|
+
# Log specific error if available
|
126
|
+
error_detail = stderr if stderr else output
|
127
|
+
logger.error(f"Error executing git commit (Exit Code {result.returncode}): {error_detail}")
|
128
|
+
return f"Error executing git commit: {error_detail}"
|
129
|
+
|
130
|
+
except FileNotFoundError:
|
131
|
+
logger.error("Git command not found.")
|
132
|
+
return "Error: git command not found."
|
133
|
+
except subprocess.TimeoutExpired:
|
134
|
+
logger.error("Git commit command timed out.")
|
135
|
+
return "Error: Git commit command timed out."
|
136
|
+
except Exception as e:
|
137
|
+
logger.error(f"Unexpected error during git commit: {e}", exc_info=logger.level <= logging.DEBUG)
|
138
|
+
return f"Error during git commit: {e}"
|
139
|
+
|
140
|
+
@function_tool
|
141
|
+
def git_push() -> str:
|
142
|
+
"""Executes 'git push' to push staged commits to the remote repository."""
|
143
|
+
logger.info("Executing git push") # Keep INFO for tool execution start
|
144
|
+
try:
|
145
|
+
result = subprocess.run(["git", "push"], capture_output=True, text=True, check=True, timeout=120) # Longer timeout for push
|
146
|
+
output = result.stdout.strip() + "\n" + result.stderr.strip() # Combine stdout/stderr
|
147
|
+
logger.debug(f"Git push raw output:\n{output}")
|
148
|
+
return f"OK: Push completed.\n{output.strip()}"
|
149
|
+
except FileNotFoundError:
|
150
|
+
logger.error("Git command not found.")
|
151
|
+
return "Error: git command not found."
|
152
|
+
except subprocess.CalledProcessError as e:
|
153
|
+
error_output = e.stdout.strip() + "\n" + e.stderr.strip()
|
154
|
+
logger.error(f"Error executing git push: {error_output}")
|
155
|
+
return f"Error executing git push: {error_output.strip()}"
|
156
|
+
except subprocess.TimeoutExpired:
|
157
|
+
logger.error("Git push command timed out.")
|
158
|
+
return "Error: Git push command timed out."
|
159
|
+
except Exception as e:
|
160
|
+
logger.error(f"Unexpected error during git push: {e}", exc_info=logger.level <= logging.DEBUG)
|
161
|
+
return f"Error during git push: {e}"
|
162
|
+
|
163
|
+
@function_tool
|
164
|
+
def run_npm_test(args: str = "") -> str:
|
165
|
+
"""Executes 'npm run test' with optional arguments."""
|
166
|
+
try:
|
167
|
+
# Use shlex.split for safer argument handling if args are provided
|
168
|
+
cmd_list = ["npm", "run", "test"] + (shlex.split(args) if args else [])
|
169
|
+
cmd_str = ' '.join(cmd_list) # For logging
|
170
|
+
logger.info(f"Executing npm test: {cmd_str}") # Keep INFO for tool execution start
|
171
|
+
result = subprocess.run(cmd_list, capture_output=True, text=True, check=False, timeout=120) # check=False to capture output on failure
|
172
|
+
output = f"Exit Code: {result.returncode}\nSTDOUT:\n{result.stdout.strip()}\nSTDERR:\n{result.stderr.strip()}"
|
173
|
+
if result.returncode == 0:
|
174
|
+
logger.debug(f"npm test completed successfully:\n{output}")
|
175
|
+
return f"OK: npm test finished.\n{output}"
|
176
|
+
else:
|
177
|
+
logger.error(f"npm test failed (Exit Code {result.returncode}):\n{output}")
|
178
|
+
return f"Error: npm test failed.\n{output}"
|
179
|
+
except FileNotFoundError:
|
180
|
+
logger.error("npm command not found. Is Node.js/npm installed and in PATH?")
|
181
|
+
return "Error: npm command not found."
|
182
|
+
except subprocess.TimeoutExpired:
|
183
|
+
logger.error("npm test command timed out.")
|
184
|
+
return "Error: npm test command timed out."
|
185
|
+
except Exception as e:
|
186
|
+
logger.error(f"Unexpected error during npm test: {e}", exc_info=logger.level <= logging.DEBUG)
|
187
|
+
return f"Error during npm test: {e}"
|
188
|
+
|
189
|
+
@function_tool
|
190
|
+
def run_pytest(args: str = "") -> str:
|
191
|
+
"""Executes 'uv run pytest' with optional arguments."""
|
192
|
+
try:
|
193
|
+
# Use shlex.split for safer argument handling
|
194
|
+
cmd_list = ["uv", "run", "pytest"] + (shlex.split(args) if args else [])
|
195
|
+
cmd_str = ' '.join(cmd_list) # For logging
|
196
|
+
logger.info(f"Executing pytest via uv: {cmd_str}") # Keep INFO for tool execution start
|
197
|
+
result = subprocess.run(cmd_list, capture_output=True, text=True, check=False, timeout=120) # check=False to capture output on failure
|
198
|
+
output = f"Exit Code: {result.returncode}\nSTDOUT:\n{result.stdout.strip()}\nSTDERR:\n{result.stderr.strip()}"
|
199
|
+
# Pytest often returns non-zero exit code on test failures, report this clearly
|
200
|
+
if result.returncode == 0:
|
201
|
+
logger.debug(f"pytest completed successfully:\n{output}")
|
202
|
+
return f"OK: pytest finished successfully.\n{output}"
|
203
|
+
else:
|
204
|
+
logger.warning(f"pytest finished with failures (Exit Code {result.returncode}):\n{output}")
|
205
|
+
# Still return "OK" from tool perspective, but indicate failure in the message
|
206
|
+
return f"OK: Pytest finished with failures (Exit Code {result.returncode}).\n{output}"
|
207
|
+
except FileNotFoundError:
|
208
|
+
logger.error("uv command not found. Is uv installed and in PATH?")
|
209
|
+
return "Error: uv command not found."
|
210
|
+
except subprocess.TimeoutExpired:
|
211
|
+
logger.error("pytest command timed out.")
|
212
|
+
return "Error: pytest command timed out."
|
213
|
+
except Exception as e:
|
214
|
+
logger.error(f"Unexpected error during pytest: {e}", exc_info=logger.level <= logging.DEBUG)
|
215
|
+
return f"Error during pytest: {e}"
|
216
|
+
|
217
|
+
# --- Agent Instructions ---
|
218
|
+
# Define clear instructions for each agent's role and capabilities.
|
219
|
+
michael_instructions = """
|
220
|
+
You are Michael Toasted, the resolute leader of the Burnt Noodles creative team.
|
221
|
+
Your primary role is to understand the user's request, break it down into actionable steps,
|
222
|
+
and delegate tasks appropriately to your team members: Fiona Flame (Git operations) and Sam Ashes (Testing).
|
223
|
+
You should only execute simple Git status checks (`git_status`, `git_diff`) yourself. Delegate all other Git actions (add, commit, push) to Fiona. Delegate all testing actions (npm test, pytest) to Sam.
|
224
|
+
Synthesize the results from your team and provide the final response to the user.
|
225
|
+
Available Function Tools (for you): git_status, git_diff.
|
226
|
+
Available Agent Tools (for delegation): Fiona_Flame, Sam_Ashes.
|
227
|
+
"""
|
228
|
+
fiona_instructions = """
|
229
|
+
You are Fiona Flame, the git specialist. Execute git commands precisely as requested using your available function tools:
|
230
|
+
`git_status`, `git_diff`, `git_add`, `git_commit`, `git_push`.
|
231
|
+
When asked to commit, analyze the diff if necessary and generate concise, informative conventional commit messages (e.g., 'feat: ...', 'fix: ...', 'refactor: ...', 'chore: ...').
|
232
|
+
Always stage changes using `git_add` before committing.
|
233
|
+
If asked to push, first ask the user (Michael) for confirmation before executing `git_push`.
|
234
|
+
If a task involves testing (like running tests after a commit), delegate it to the Sam_Ashes agent tool.
|
235
|
+
For tasks outside your Git domain, report back to Michael; do not use the Michael_Toasted tool directly.
|
236
|
+
Available Function Tools: git_status, git_diff, git_add, git_commit, git_push.
|
237
|
+
Available Agent Tools: Sam_Ashes.
|
238
|
+
"""
|
239
|
+
sam_instructions = """
|
240
|
+
You are Sam Ashes, the meticulous testing operative. Execute test commands using your available function tools: `run_npm_test` or `run_pytest`.
|
241
|
+
Interpret the results: Report failures immediately and clearly. If tests pass, consider running with coverage (e.g., using `uv run pytest --cov` via the `run_pytest` tool) if appropriate or requested, and report the coverage summary.
|
242
|
+
For tasks outside testing (e.g., needing code changes before testing, or git operations), refer back to Michael; do not use the Michael_Toasted or Fiona_Flame tools directly.
|
243
|
+
Available Function Tools: run_npm_test, run_pytest.
|
244
|
+
Available Agent Tools: None (Report back to Michael for delegation).
|
245
|
+
"""
|
246
|
+
|
247
|
+
# --- Blueprint Definition ---
|
248
|
+
# Inherits from BlueprintBase, defines metadata, creates agents, and sets up delegation.
|
249
|
+
class BurntNoodlesBlueprint(BlueprintBase):
|
250
|
+
"""
|
251
|
+
Burnt Noodles Blueprint: A multi-agent team demonstrating Git operations and testing workflows.
|
252
|
+
- Michael Toasted: Coordinator, delegates tasks.
|
253
|
+
- Fiona Flame: Handles Git commands (status, diff, add, commit, push).
|
254
|
+
- Sam Ashes: Handles test execution (npm, pytest).
|
255
|
+
"""
|
256
|
+
# Class variable for blueprint metadata, conforming to BlueprintBase structure.
|
257
|
+
metadata: ClassVar[Dict[str, Any]] = {
|
258
|
+
"name": "BurntNoodlesBlueprint",
|
259
|
+
"title": "Burnt Noodles",
|
260
|
+
"description": "A multi-agent team managing Git operations and code testing.",
|
261
|
+
"version": "1.1.0", # Incremented version
|
262
|
+
"author": "Open Swarm Team (Refactored)",
|
263
|
+
"tags": ["git", "test", "multi-agent", "collaboration", "refactor"],
|
264
|
+
"required_mcp_servers": [], # No external MCP servers needed for core functionality
|
265
|
+
}
|
266
|
+
|
267
|
+
# Caches for OpenAI client and Model instances to avoid redundant creation.
|
268
|
+
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
269
|
+
_model_instance_cache: Dict[str, Model] = {}
|
270
|
+
|
271
|
+
def _get_model_instance(self, profile_name: str) -> Model:
|
272
|
+
"""
|
273
|
+
Retrieves or creates an LLM Model instance based on the configuration profile.
|
274
|
+
Handles client instantiation and caching. Uses OpenAIChatCompletionsModel.
|
275
|
+
Args:
|
276
|
+
profile_name: The name of the LLM profile to use (e.g., 'default').
|
277
|
+
Returns:
|
278
|
+
An instance of the configured Model.
|
279
|
+
Raises:
|
280
|
+
ValueError: If configuration is missing or invalid.
|
281
|
+
"""
|
282
|
+
# Check cache first
|
283
|
+
if profile_name in self._model_instance_cache:
|
284
|
+
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
285
|
+
return self._model_instance_cache[profile_name]
|
286
|
+
|
287
|
+
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
288
|
+
# Retrieve profile data using BlueprintBase helper method
|
289
|
+
profile_data = self.get_llm_profile(profile_name)
|
290
|
+
if not profile_data:
|
291
|
+
# Critical error if the profile (or default fallback) isn't found
|
292
|
+
logger.critical(f"Cannot create Model instance: LLM profile '{profile_name}' (or 'default') not found in configuration.")
|
293
|
+
raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
|
294
|
+
|
295
|
+
provider = profile_data.get("provider", "openai").lower()
|
296
|
+
model_name = profile_data.get("model")
|
297
|
+
if not model_name:
|
298
|
+
logger.critical(f"LLM profile '{profile_name}' is missing the required 'model' key.")
|
299
|
+
raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
|
300
|
+
|
301
|
+
# Ensure we only handle OpenAI for now
|
302
|
+
if provider != "openai":
|
303
|
+
logger.error(f"Unsupported LLM provider '{provider}' in profile '{profile_name}'. Only 'openai' is supported in this blueprint.")
|
304
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
305
|
+
|
306
|
+
# Create or retrieve cached OpenAI client instance
|
307
|
+
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
308
|
+
if client_cache_key not in self._openai_client_cache:
|
309
|
+
# Prepare arguments for AsyncOpenAI, filtering out None values
|
310
|
+
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
311
|
+
filtered_client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
312
|
+
log_client_kwargs = {k:v for k,v in filtered_client_kwargs.items() if k != 'api_key'} # Don't log API key
|
313
|
+
logger.debug(f"Creating new AsyncOpenAI client for profile '{profile_name}' with config: {log_client_kwargs}")
|
314
|
+
try:
|
315
|
+
# Create and cache the client
|
316
|
+
self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_client_kwargs)
|
317
|
+
except Exception as e:
|
318
|
+
logger.error(f"Failed to create AsyncOpenAI client for profile '{profile_name}': {e}", exc_info=True)
|
319
|
+
raise ValueError(f"Failed to initialize OpenAI client for profile '{profile_name}': {e}") from e
|
320
|
+
|
321
|
+
openai_client_instance = self._openai_client_cache[client_cache_key]
|
322
|
+
|
323
|
+
# Instantiate the specific Model implementation (OpenAIChatCompletionsModel)
|
324
|
+
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') with client instance for profile '{profile_name}'.")
|
325
|
+
try:
|
326
|
+
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client_instance)
|
327
|
+
# Cache the model instance
|
328
|
+
self._model_instance_cache[profile_name] = model_instance
|
329
|
+
return model_instance
|
330
|
+
except Exception as e:
|
331
|
+
logger.error(f"Failed to instantiate OpenAIChatCompletionsModel for profile '{profile_name}': {e}", exc_info=True)
|
332
|
+
raise ValueError(f"Failed to initialize LLM provider for profile '{profile_name}': {e}") from e
|
333
|
+
|
334
|
+
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
335
|
+
"""
|
336
|
+
Creates the Burnt Noodles agent team: Michael (Coordinator), Fiona (Git), Sam (Testing).
|
337
|
+
Sets up tools and agent-as-tool delegation.
|
338
|
+
Args:
|
339
|
+
mcp_servers: List of started MCP server instances (not used by this BP).
|
340
|
+
Returns:
|
341
|
+
The starting agent instance (Michael Toasted).
|
342
|
+
"""
|
343
|
+
logger.debug("Creating Burnt Noodles agent team...")
|
344
|
+
# Clear caches at the start of agent creation for this run
|
345
|
+
self._model_instance_cache = {}
|
346
|
+
self._openai_client_cache = {}
|
347
|
+
|
348
|
+
# Determine the LLM profile to use (e.g., from config or default)
|
349
|
+
default_profile_name = self.config.get("llm_profile", "default")
|
350
|
+
logger.debug(f"Using LLM profile '{default_profile_name}' for all Burnt Noodles agents.")
|
351
|
+
# Get the single Model instance to share among agents (or create if needed)
|
352
|
+
default_model_instance = self._get_model_instance(default_profile_name)
|
353
|
+
|
354
|
+
# Instantiate the specialist agents first
|
355
|
+
# Fiona gets Git function tools
|
356
|
+
fiona_flame = Agent(
|
357
|
+
name="Fiona_Flame", # Use names valid as tool names
|
358
|
+
model=default_model_instance,
|
359
|
+
instructions=fiona_instructions,
|
360
|
+
tools=[git_status, git_diff, git_add, git_commit, git_push] # Agent tools added later
|
361
|
+
)
|
362
|
+
# Sam gets Testing function tools
|
363
|
+
sam_ashes = Agent(
|
364
|
+
name="Sam_Ashes", # Use names valid as tool names
|
365
|
+
model=default_model_instance,
|
366
|
+
instructions=sam_instructions,
|
367
|
+
tools=[run_npm_test, run_pytest] # Agent tools added later
|
368
|
+
)
|
369
|
+
|
370
|
+
# Instantiate the coordinator agent (Michael)
|
371
|
+
# Michael gets limited function tools and the specialist agents as tools
|
372
|
+
michael_toasted = Agent(
|
373
|
+
name="Michael_Toasted",
|
374
|
+
model=default_model_instance,
|
375
|
+
instructions=michael_instructions,
|
376
|
+
tools=[
|
377
|
+
# Michael's direct function tools (limited scope)
|
378
|
+
git_status,
|
379
|
+
git_diff,
|
380
|
+
# Specialist agents exposed as tools for delegation
|
381
|
+
fiona_flame.as_tool(
|
382
|
+
tool_name="Fiona_Flame", # Explicit tool name
|
383
|
+
tool_description="Delegate Git operations (add, commit, push) or complex status/diff queries to Fiona."
|
384
|
+
),
|
385
|
+
sam_ashes.as_tool(
|
386
|
+
tool_name="Sam_Ashes", # Explicit tool name
|
387
|
+
tool_description="Delegate testing tasks (npm test, pytest) to Sam."
|
388
|
+
),
|
389
|
+
],
|
390
|
+
mcp_servers=mcp_servers # Pass along MCP servers if needed (though not used here)
|
391
|
+
)
|
392
|
+
|
393
|
+
# Add cross-delegation tools *after* all agents are instantiated
|
394
|
+
# Fiona can delegate testing to Sam
|
395
|
+
fiona_flame.tools.append(
|
396
|
+
sam_ashes.as_tool(tool_name="Sam_Ashes", tool_description="Delegate testing tasks (npm test, pytest) to Sam.")
|
397
|
+
)
|
398
|
+
# Sam can delegate Git tasks back to Fiona (as per instructions, Sam should report to Michael,
|
399
|
+
# but having the tool technically available might be useful in complex future scenarios,
|
400
|
+
# rely on prompt engineering to prevent direct calls unless intended).
|
401
|
+
# sam_ashes.tools.append(
|
402
|
+
# fiona_flame.as_tool(tool_name="Fiona_Flame", tool_description="Delegate Git operations back to Fiona if needed.")
|
403
|
+
# )
|
404
|
+
|
405
|
+
logger.debug("Burnt Noodles agent team created successfully. Michael Toasted is the starting agent.")
|
406
|
+
# Return the coordinator agent as the entry point for the Runner
|
407
|
+
return michael_toasted
|
408
|
+
|
409
|
+
# Standard Python entry point for direct script execution
|
410
|
+
if __name__ == "__main__":
|
411
|
+
# Call the main class method from BlueprintBase to handle CLI parsing and execution.
|
412
|
+
BurntNoodlesBlueprint.main()
|