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.
@@ -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()