open-swarm 0.1.1744936125__py3-none-any.whl → 0.1.1744936234__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.1744936125.dist-info → open_swarm-0.1.1744936234.dist-info}/METADATA +1 -1
- {open_swarm-0.1.1744936125.dist-info → open_swarm-0.1.1744936234.dist-info}/RECORD +16 -19
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +28 -0
- swarm/blueprints/divine_code/blueprint_divine_code.py +26 -0
- swarm/blueprints/django_chat/blueprint_django_chat.py +15 -4
- swarm/blueprints/echocraft/blueprint_echocraft.py +9 -2
- swarm/blueprints/family_ties/blueprint_family_ties.py +28 -0
- swarm/blueprints/gaggle/blueprint_gaggle.py +117 -15
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +10 -0
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +47 -29
- swarm/blueprints/omniplex/blueprint_omniplex.py +21 -0
- swarm/blueprints/rue_code/blueprint_rue_code.py +24 -25
- swarm/blueprints/suggestion/blueprint_suggestion.py +35 -12
- swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +0 -304
- swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +0 -285
- swarm/blueprints/gotchaman/blueprint_gotchaman.py +0 -232
- {open_swarm-0.1.1744936125.dist-info → open_swarm-0.1.1744936234.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1744936125.dist-info → open_swarm-0.1.1744936234.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1744936125.dist-info → open_swarm-0.1.1744936234.dist-info}/licenses/LICENSE +0 -0
@@ -1,285 +0,0 @@
|
|
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()
|
@@ -1,232 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Gotchaman: CLI Automation Blueprint
|
3
|
-
|
4
|
-
This blueprint provides CLI automation capabilities using a team of agents:
|
5
|
-
- Ken (Coordinator)
|
6
|
-
- Joe (Runner - executes commands/file ops)
|
7
|
-
- Jun (Logger - hypothetical monitoring via MCP)
|
8
|
-
- Jinpei (Advisor - hypothetical suggestion via MCP)
|
9
|
-
- Ryu (Reviewer - hypothetical insights via MCP)
|
10
|
-
|
11
|
-
Uses BlueprintBase, @function_tool for local commands, and agent-as-tool delegation.
|
12
|
-
"""
|
13
|
-
|
14
|
-
import os
|
15
|
-
import sys
|
16
|
-
import logging
|
17
|
-
import subprocess
|
18
|
-
import shlex # For safe command splitting
|
19
|
-
from pathlib import Path # Use pathlib
|
20
|
-
from typing import Dict, Any, List, ClassVar, Optional
|
21
|
-
|
22
|
-
# Ensure src is in path for BlueprintBase import
|
23
|
-
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
24
|
-
src_path = os.path.join(project_root, 'src')
|
25
|
-
if src_path not in sys.path: sys.path.insert(0, src_path)
|
26
|
-
|
27
|
-
try:
|
28
|
-
from agents import Agent, Tool, function_tool, Runner
|
29
|
-
from agents.mcp import MCPServer
|
30
|
-
from agents.models.interface import Model
|
31
|
-
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
32
|
-
from openai import AsyncOpenAI
|
33
|
-
from swarm.extensions.blueprint.blueprint_base import BlueprintBase
|
34
|
-
except ImportError as e:
|
35
|
-
print(f"ERROR: Import failed in GotchamanBlueprint: {e}. Check dependencies.")
|
36
|
-
print(f"sys.path: {sys.path}")
|
37
|
-
sys.exit(1)
|
38
|
-
|
39
|
-
logger = logging.getLogger(__name__)
|
40
|
-
|
41
|
-
# --- Function Tools ---
|
42
|
-
@function_tool
|
43
|
-
def execute_command(command: str) -> str:
|
44
|
-
"""Executes a shell command and returns its stdout and stderr."""
|
45
|
-
if not command: return "Error: No command provided."
|
46
|
-
logger.info(f"Tool: Executing command: {command}")
|
47
|
-
try:
|
48
|
-
# Use shell=True cautiously, consider splitting if possible for safer execution
|
49
|
-
result = subprocess.run(
|
50
|
-
command,
|
51
|
-
shell=True, # Be cautious with shell=True
|
52
|
-
check=False, # Capture output even on error
|
53
|
-
capture_output=True,
|
54
|
-
text=True,
|
55
|
-
timeout=120
|
56
|
-
)
|
57
|
-
output = f"Exit Code: {result.returncode}\nSTDOUT:\n{result.stdout.strip()}\nSTDERR:\n{result.stderr.strip()}"
|
58
|
-
if result.returncode == 0:
|
59
|
-
logger.debug(f"Command successful:\n{output}")
|
60
|
-
return f"OK: Command executed.\n{output}"
|
61
|
-
else:
|
62
|
-
logger.error(f"Command failed:\n{output}")
|
63
|
-
return f"Error: Command failed.\n{output}"
|
64
|
-
except FileNotFoundError:
|
65
|
-
# This error is less likely with shell=True unless the shell itself is missing
|
66
|
-
logger.error(f"Error executing command '{command}': Shell or command not found.")
|
67
|
-
return f"Error: Shell or command '{command.split()[0]}' not found."
|
68
|
-
except subprocess.TimeoutExpired:
|
69
|
-
logger.error(f"Command '{command}' timed out.")
|
70
|
-
return f"Error: Command '{command}' timed out."
|
71
|
-
except Exception as e:
|
72
|
-
logger.error(f"Unexpected error executing command '{command}': {e}", exc_info=logger.level <= logging.DEBUG)
|
73
|
-
return f"Error: Unexpected error during command execution: {e}"
|
74
|
-
|
75
|
-
@function_tool
|
76
|
-
def read_file(path: str) -> str:
|
77
|
-
"""Reads the content of a file at the specified path."""
|
78
|
-
if not path: return "Error: No file path provided."
|
79
|
-
logger.info(f"Tool: Reading file at: {path}")
|
80
|
-
try:
|
81
|
-
file_path = Path(path).resolve()
|
82
|
-
# Optional: Add security check to ensure path is within allowed bounds if needed
|
83
|
-
# cwd = Path.cwd()
|
84
|
-
# if not str(file_path).startswith(str(cwd)):
|
85
|
-
# logger.warning(f"Attempt to read file outside current working directory: {path}")
|
86
|
-
# return f"Error: Access denied to path: {path}"
|
87
|
-
if not file_path.is_file():
|
88
|
-
logger.error(f"File not found at: {file_path}")
|
89
|
-
return f"Error: File not found at path: {path}"
|
90
|
-
content = file_path.read_text(encoding="utf-8")
|
91
|
-
logger.debug(f"Read {len(content)} characters from {file_path}.")
|
92
|
-
return f"OK: Content of {path}:\n{content}"
|
93
|
-
except Exception as e:
|
94
|
-
logger.error(f"Error reading file at {path}: {e}", exc_info=logger.level <= logging.DEBUG)
|
95
|
-
return f"Error reading file '{path}': {e}"
|
96
|
-
|
97
|
-
@function_tool
|
98
|
-
def write_file(path: str, content: str) -> str:
|
99
|
-
"""Writes content to a file at the specified path, overwriting if it exists."""
|
100
|
-
if not path: return "Error: No file path provided."
|
101
|
-
logger.info(f"Tool: Writing {len(content)} characters to file at: {path}")
|
102
|
-
try:
|
103
|
-
file_path = Path(path).resolve()
|
104
|
-
# Optional: Add security check
|
105
|
-
# cwd = Path.cwd()
|
106
|
-
# if not str(file_path).startswith(str(cwd)):
|
107
|
-
# logger.warning(f"Attempt to write file outside current working directory: {path}")
|
108
|
-
# return f"Error: Access denied to path: {path}"
|
109
|
-
|
110
|
-
file_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
|
111
|
-
file_path.write_text(content, encoding="utf-8")
|
112
|
-
logger.debug(f"Successfully wrote to {file_path}.")
|
113
|
-
return f"OK: Successfully wrote to {path}."
|
114
|
-
except Exception as e:
|
115
|
-
logger.error(f"Error writing file at {path}: {e}", exc_info=logger.level <= logging.DEBUG)
|
116
|
-
return f"Error writing file '{path}': {e}"
|
117
|
-
|
118
|
-
# --- Define the Blueprint ---
|
119
|
-
class GotchamanBlueprint(BlueprintBase):
|
120
|
-
"""Gotchaman: CLI Automation Blueprint using BlueprintBase."""
|
121
|
-
|
122
|
-
metadata: ClassVar[Dict[str, Any]] = {
|
123
|
-
"name": "GotchamanBlueprint",
|
124
|
-
"title": "Gotchaman: CLI Automation",
|
125
|
-
"description": (
|
126
|
-
"A blueprint for automating CLI tasks using a team of agents (Ken, Joe, Jun, Jinpei, Ryu) "
|
127
|
-
"with specific roles and MCP/tool access."
|
128
|
-
),
|
129
|
-
"version": "1.1.0", # Refactored version
|
130
|
-
"author": "Open Swarm Team (Refactored)",
|
131
|
-
"tags": ["cli", "automation", "multi-agent", "mcp", "slack", "monday"],
|
132
|
-
# List only servers directly used by refactored agents
|
133
|
-
"required_mcp_servers": ["slack", "mondayDotCom", "basic-memory", "mcp-npx-fetch"],
|
134
|
-
"env_vars": ["SLACK_API_KEY", "MONDAY_API_KEY"]
|
135
|
-
}
|
136
|
-
|
137
|
-
# Caches
|
138
|
-
_openai_client_cache: Dict[str, AsyncOpenAI] = {}
|
139
|
-
_model_instance_cache: Dict[str, Model] = {}
|
140
|
-
|
141
|
-
# --- Model Instantiation Helper --- (Standard helper)
|
142
|
-
def _get_model_instance(self, profile_name: str) -> Model:
|
143
|
-
"""Retrieves or creates an LLM Model instance."""
|
144
|
-
# ... (Implementation is the same as previous refactors) ...
|
145
|
-
if profile_name in self._model_instance_cache:
|
146
|
-
logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
|
147
|
-
return self._model_instance_cache[profile_name]
|
148
|
-
logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
|
149
|
-
profile_data = self.get_llm_profile(profile_name)
|
150
|
-
if not profile_data: raise ValueError(f"Missing LLM profile '{profile_name}'.")
|
151
|
-
provider = profile_data.get("provider", "openai").lower()
|
152
|
-
model_name = profile_data.get("model")
|
153
|
-
if not model_name: raise ValueError(f"Missing 'model' in profile '{profile_name}'.")
|
154
|
-
if provider != "openai": raise ValueError(f"Unsupported provider: {provider}")
|
155
|
-
client_cache_key = f"{provider}_{profile_data.get('base_url')}"
|
156
|
-
if client_cache_key not in self._openai_client_cache:
|
157
|
-
client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
|
158
|
-
filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
|
159
|
-
log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
|
160
|
-
logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}': {log_kwargs}")
|
161
|
-
try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
|
162
|
-
except Exception as e: raise ValueError(f"Failed to init client: {e}") from e
|
163
|
-
client = self._openai_client_cache[client_cache_key]
|
164
|
-
logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
|
165
|
-
try:
|
166
|
-
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
|
167
|
-
self._model_instance_cache[profile_name] = model_instance
|
168
|
-
return model_instance
|
169
|
-
except Exception as e: raise ValueError(f"Failed to init LLM: {e}") from e
|
170
|
-
|
171
|
-
|
172
|
-
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
173
|
-
"""Creates the Gotchaman agent team and returns Ken (Coordinator)."""
|
174
|
-
logger.debug("Creating Gotchaman agent team...")
|
175
|
-
self._model_instance_cache = {}
|
176
|
-
self._openai_client_cache = {}
|
177
|
-
|
178
|
-
default_profile_name = self.config.get("llm_profile", "default")
|
179
|
-
logger.debug(f"Using LLM profile '{default_profile_name}' for Gotchaman agents.")
|
180
|
-
model_instance = self._get_model_instance(default_profile_name)
|
181
|
-
|
182
|
-
# Helper to filter MCP servers
|
183
|
-
def get_agent_mcps(names: List[str]) -> List[MCPServer]:
|
184
|
-
return [s for s in mcp_servers if s.name in names]
|
185
|
-
|
186
|
-
# --- Agent Instructions ---
|
187
|
-
ken_instructions = "You are Ken, the Coordinator for Gotchaman team. Your team: Joe (Runner), Jun (Logger), Jinpei (Advisor), and Ryu (Reviewer). Analyze the user request and delegate tasks to the appropriate agent using their Agent Tool. Synthesize their responses for the final output."
|
188
|
-
joe_instructions = "You are Joe, the Runner. Execute shell commands, read files, or write files using your function tools (`execute_command`, `read_file`, `write_file`) as requested by Ken. Report the outcome."
|
189
|
-
jun_instructions = "You are Jun, the Logger. Receive information or instructions from Ken. Use the `slack` MCP tool to log messages or feedback to a designated channel (details provided by Ken or pre-configured). Report success/failure of logging back to Ken."
|
190
|
-
jinpei_instructions = "You are Jinpei, the Advisor. Receive context from Ken. Use the `mcp-npx-fetch` MCP tool to fetch relevant documentation or examples based on the context. Provide concise suggestions or relevant snippets back to Ken."
|
191
|
-
ryu_instructions = "You are Ryu, the Reviewer. Receive outputs or code snippets from Ken. Use the `basic-memory` MCP tool to recall previous related outputs or guidelines if necessary. Provide insightful review comments or quality checks back to Ken."
|
192
|
-
|
193
|
-
# Instantiate agents
|
194
|
-
joe_agent = Agent(
|
195
|
-
name="Joe", model=model_instance, instructions=joe_instructions,
|
196
|
-
tools=[execute_command, read_file, write_file], # Joe has the function tools
|
197
|
-
mcp_servers=[] # Joe doesn't directly use MCPs listed
|
198
|
-
)
|
199
|
-
jun_agent = Agent(
|
200
|
-
name="Jun", model=model_instance, instructions=jun_instructions,
|
201
|
-
tools=[], # Jun uses MCP
|
202
|
-
mcp_servers=get_agent_mcps(["slack"])
|
203
|
-
)
|
204
|
-
jinpei_agent = Agent(
|
205
|
-
name="Jinpei", model=model_instance, instructions=jinpei_instructions,
|
206
|
-
tools=[], # Jinpei uses MCP
|
207
|
-
mcp_servers=get_agent_mcps(["mcp-npx-fetch"])
|
208
|
-
)
|
209
|
-
ryu_agent = Agent(
|
210
|
-
name="Ryu", model=model_instance, instructions=ryu_instructions,
|
211
|
-
tools=[], # Ryu uses MCP
|
212
|
-
mcp_servers=get_agent_mcps(["basic-memory"])
|
213
|
-
)
|
214
|
-
# Coordinator - Ken
|
215
|
-
ken_agent = Agent(
|
216
|
-
name="Ken", model=model_instance, instructions=ken_instructions,
|
217
|
-
tools=[ # Ken delegates to others via agent tools
|
218
|
-
joe_agent.as_tool(tool_name="Joe", tool_description="Delegate command execution or file operations to Joe."),
|
219
|
-
jun_agent.as_tool(tool_name="Jun", tool_description="Delegate logging tasks via Slack to Jun."),
|
220
|
-
jinpei_agent.as_tool(tool_name="Jinpei", tool_description="Delegate fetching docs/examples to Jinpei."),
|
221
|
-
ryu_agent.as_tool(tool_name="Ryu", tool_description="Delegate review tasks or recall past info via Ryu."),
|
222
|
-
],
|
223
|
-
# Ken might use memory directly, or coordinate access via Ryu? Assigning for potential direct use.
|
224
|
-
mcp_servers=get_agent_mcps(["basic-memory"])
|
225
|
-
)
|
226
|
-
|
227
|
-
logger.debug("Gotchaman agents created. Starting with Ken.")
|
228
|
-
return ken_agent
|
229
|
-
|
230
|
-
# Standard Python entry point
|
231
|
-
if __name__ == "__main__":
|
232
|
-
GotchamanBlueprint.main()
|
File without changes
|
{open_swarm-0.1.1744936125.dist-info → open_swarm-0.1.1744936234.dist-info}/entry_points.txt
RENAMED
File without changes
|
{open_swarm-0.1.1744936125.dist-info → open_swarm-0.1.1744936234.dist-info}/licenses/LICENSE
RENAMED
File without changes
|