swarmai 1.0.0__tar.gz
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.
- swarmai-1.0.0/PKG-INFO +92 -0
- swarmai-1.0.0/README.md +78 -0
- swarmai-1.0.0/pyproject.toml +28 -0
- swarmai-1.0.0/setup.cfg +4 -0
- swarmai-1.0.0/swarm/__init__.py +6 -0
- swarmai-1.0.0/swarm/cli.py +119 -0
- swarmai-1.0.0/swarm/core.py +167 -0
- swarmai-1.0.0/swarm/tools.py +76 -0
- swarmai-1.0.0/swarm/utils.py +82 -0
- swarmai-1.0.0/swarmai.egg-info/PKG-INFO +92 -0
- swarmai-1.0.0/swarmai.egg-info/SOURCES.txt +13 -0
- swarmai-1.0.0/swarmai.egg-info/dependency_links.txt +1 -0
- swarmai-1.0.0/swarmai.egg-info/entry_points.txt +2 -0
- swarmai-1.0.0/swarmai.egg-info/requires.txt +2 -0
- swarmai-1.0.0/swarmai.egg-info/top_level.txt +1 -0
swarmai-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swarmai
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A highly flexible multi-agent ReAct framework powered by Google Gemini
|
|
5
|
+
Author-email: ProgVM <progvminc@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/ProgVM
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: google-genai
|
|
13
|
+
Requires-Dist: duckduckgo-search
|
|
14
|
+
|
|
15
|
+
# 🐝 Swarm
|
|
16
|
+
|
|
17
|
+
**Swarm** is an advanced, open-source multi-agent intelligence framework designed for autonomous collaboration. Powered by Google's **Gemini 3.1 Flash Lite**, Swarm allows multiple agents to interact, use tools, execute code, and perform web searches in a shared environment.
|
|
18
|
+
|
|
19
|
+
## 🚀 Key Features
|
|
20
|
+
|
|
21
|
+
- **N-Agent Collaboration**: Run any number of agents in a single session.
|
|
22
|
+
- **Autonomous Tool Usage**: Agents can perform Google searches and execute Shell commands.
|
|
23
|
+
- **Agent Sandboxing**: Per-agent blacklists for terminal commands and file paths.
|
|
24
|
+
- **Turn Management**: Agents can autonomously pass turns to specific peers using `pass_turn`.
|
|
25
|
+
- **ReAct Loop**: High-level reasoning before acting, supporting multi-step tool execution.
|
|
26
|
+
- **Files API Support**: Agents can upload and analyze PDF, images, and logs via Google Files API.
|
|
27
|
+
- **Persistent Sessions**: Save and load full agent histories and environment states.
|
|
28
|
+
|
|
29
|
+
## 🛠 Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Clone the repository
|
|
33
|
+
git clone https://github.com/ProgVM/swarm
|
|
34
|
+
cd swarm
|
|
35
|
+
|
|
36
|
+
# Install dependencies
|
|
37
|
+
pip install .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or:
|
|
41
|
+
```bash
|
|
42
|
+
pip install swarmai
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 💻 CLI Usage
|
|
46
|
+
|
|
47
|
+
Start a basic conversation with two agents:
|
|
48
|
+
```bash
|
|
49
|
+
swarm --keys YOUR_API_KEY --agents_count 2
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Start an autonomous coding session:
|
|
53
|
+
```bash
|
|
54
|
+
swarm --first_msg "Write a web scraper in Python and test it" \
|
|
55
|
+
--sys1 "You are an Expert Coder" \
|
|
56
|
+
--sys2 "You are a Security Auditor" \
|
|
57
|
+
--cmd_timeout 600
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Advanced Configuration
|
|
61
|
+
Swarm supports individual parameters for every agent using `aiN_` prefixes:
|
|
62
|
+
- `--ai1_name "Architect"`
|
|
63
|
+
- `--ai1_model "gemini-2.0-flash"`
|
|
64
|
+
- `--ai2_name "Tester"`
|
|
65
|
+
- `--ai2_temp 0.2`
|
|
66
|
+
|
|
67
|
+
## ⚙️ Configuration File
|
|
68
|
+
You can use a JSON config to manage complex Swarms:
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"agents_count": 3,
|
|
72
|
+
"model": "gemini-3.1-flash-lite",
|
|
73
|
+
"cmd_blacklist": ["rm -rf", "format"],
|
|
74
|
+
"no_pause": true
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
Run with: `swarm --config my_swarm.json`
|
|
78
|
+
|
|
79
|
+
## ⌨️ Command Center (Interactive Menu)
|
|
80
|
+
Press `Ctrl+C` at any time to:
|
|
81
|
+
- Rotate API Keys.
|
|
82
|
+
- Toggle reading pauses.
|
|
83
|
+
- Save the current state.
|
|
84
|
+
- Change logging levels (DEBUG, INFO, WARNING).
|
|
85
|
+
- Inject manual directives into agents' minds.
|
|
86
|
+
|
|
87
|
+
## 🤝 Contributing
|
|
88
|
+
Developed by **ProgVM**.
|
|
89
|
+
- Email: progvminc@gmail.com
|
|
90
|
+
- GitHub: [https://github.com/ProgVM](https://github.com/ProgVM)
|
|
91
|
+
|
|
92
|
+
License: MIT
|
swarmai-1.0.0/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# 🐝 Swarm
|
|
2
|
+
|
|
3
|
+
**Swarm** is an advanced, open-source multi-agent intelligence framework designed for autonomous collaboration. Powered by Google's **Gemini 3.1 Flash Lite**, Swarm allows multiple agents to interact, use tools, execute code, and perform web searches in a shared environment.
|
|
4
|
+
|
|
5
|
+
## 🚀 Key Features
|
|
6
|
+
|
|
7
|
+
- **N-Agent Collaboration**: Run any number of agents in a single session.
|
|
8
|
+
- **Autonomous Tool Usage**: Agents can perform Google searches and execute Shell commands.
|
|
9
|
+
- **Agent Sandboxing**: Per-agent blacklists for terminal commands and file paths.
|
|
10
|
+
- **Turn Management**: Agents can autonomously pass turns to specific peers using `pass_turn`.
|
|
11
|
+
- **ReAct Loop**: High-level reasoning before acting, supporting multi-step tool execution.
|
|
12
|
+
- **Files API Support**: Agents can upload and analyze PDF, images, and logs via Google Files API.
|
|
13
|
+
- **Persistent Sessions**: Save and load full agent histories and environment states.
|
|
14
|
+
|
|
15
|
+
## 🛠 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Clone the repository
|
|
19
|
+
git clone https://github.com/ProgVM/swarm
|
|
20
|
+
cd swarm
|
|
21
|
+
|
|
22
|
+
# Install dependencies
|
|
23
|
+
pip install .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or:
|
|
27
|
+
```bash
|
|
28
|
+
pip install swarmai
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 💻 CLI Usage
|
|
32
|
+
|
|
33
|
+
Start a basic conversation with two agents:
|
|
34
|
+
```bash
|
|
35
|
+
swarm --keys YOUR_API_KEY --agents_count 2
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Start an autonomous coding session:
|
|
39
|
+
```bash
|
|
40
|
+
swarm --first_msg "Write a web scraper in Python and test it" \
|
|
41
|
+
--sys1 "You are an Expert Coder" \
|
|
42
|
+
--sys2 "You are a Security Auditor" \
|
|
43
|
+
--cmd_timeout 600
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Advanced Configuration
|
|
47
|
+
Swarm supports individual parameters for every agent using `aiN_` prefixes:
|
|
48
|
+
- `--ai1_name "Architect"`
|
|
49
|
+
- `--ai1_model "gemini-2.0-flash"`
|
|
50
|
+
- `--ai2_name "Tester"`
|
|
51
|
+
- `--ai2_temp 0.2`
|
|
52
|
+
|
|
53
|
+
## ⚙️ Configuration File
|
|
54
|
+
You can use a JSON config to manage complex Swarms:
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"agents_count": 3,
|
|
58
|
+
"model": "gemini-3.1-flash-lite",
|
|
59
|
+
"cmd_blacklist": ["rm -rf", "format"],
|
|
60
|
+
"no_pause": true
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
Run with: `swarm --config my_swarm.json`
|
|
64
|
+
|
|
65
|
+
## ⌨️ Command Center (Interactive Menu)
|
|
66
|
+
Press `Ctrl+C` at any time to:
|
|
67
|
+
- Rotate API Keys.
|
|
68
|
+
- Toggle reading pauses.
|
|
69
|
+
- Save the current state.
|
|
70
|
+
- Change logging levels (DEBUG, INFO, WARNING).
|
|
71
|
+
- Inject manual directives into agents' minds.
|
|
72
|
+
|
|
73
|
+
## 🤝 Contributing
|
|
74
|
+
Developed by **ProgVM**.
|
|
75
|
+
- Email: progvminc@gmail.com
|
|
76
|
+
- GitHub: [https://github.com/ProgVM](https://github.com/ProgVM)
|
|
77
|
+
|
|
78
|
+
License: MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "swarmai"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="ProgVM", email="progvminc@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A highly flexible multi-agent ReAct framework powered by Google Gemini"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"google-genai",
|
|
21
|
+
"duckduckgo-search",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
"Homepage" = "https://github.com/ProgVM"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
swarm = "swarm.cli:run"
|
swarmai-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from .utils import Colors, setup_logger, smart_sleep, Serializer
|
|
6
|
+
from .core import SwarmSession
|
|
7
|
+
|
|
8
|
+
def parse_args():
|
|
9
|
+
parser = argparse.ArgumentParser(description="Swarm Framework")
|
|
10
|
+
parser.add_argument("--agents_count", type=int, default=2)
|
|
11
|
+
parser.add_argument("--model", default="gemini-3.1-flash-lite")
|
|
12
|
+
parser.add_argument("--first_msg", default="Initialize Swarm and greet the user.")
|
|
13
|
+
parser.add_argument("--keys", nargs="+")
|
|
14
|
+
parser.add_argument("--config", help="Path to config JSON")
|
|
15
|
+
parser.add_argument("--load", help="Load session JSON")
|
|
16
|
+
parser.add_argument("--log_level", default="INFO")
|
|
17
|
+
parser.add_argument("--no_pause", action="store_true")
|
|
18
|
+
parser.add_argument("--save_file", default="swarm_session.json")
|
|
19
|
+
|
|
20
|
+
# Global tool settings
|
|
21
|
+
parser.add_argument("--max_results", type=int, default=5)
|
|
22
|
+
parser.add_argument("--cmd_timeout", type=int, default=300)
|
|
23
|
+
parser.add_argument("--cmd_blacklist", nargs="*", default=[])
|
|
24
|
+
parser.add_argument("--file_blacklist", nargs="*", default=[])
|
|
25
|
+
|
|
26
|
+
# Defaults
|
|
27
|
+
parser.add_argument("--sys1", default="You are Agent 1.")
|
|
28
|
+
parser.add_argument("--sys2", default="You are Agent 2.")
|
|
29
|
+
parser.add_argument("--temp", type=float, default=0.7)
|
|
30
|
+
|
|
31
|
+
return parser.parse_known_args()
|
|
32
|
+
|
|
33
|
+
def run():
|
|
34
|
+
args, unknown = parse_args()
|
|
35
|
+
logger = setup_logger(args.log_level)
|
|
36
|
+
|
|
37
|
+
# 1. Load config if exists (Overrides defaults)
|
|
38
|
+
if args.config and os.path.exists(args.config):
|
|
39
|
+
with open(args.config, 'r') as f:
|
|
40
|
+
cdata = json.load(f)
|
|
41
|
+
for k, v in cdata.items(): setattr(args, k, v)
|
|
42
|
+
|
|
43
|
+
# 2. Collect API Keys
|
|
44
|
+
keys = args.keys or [os.getenv("GOOGLE_API_KEY")]
|
|
45
|
+
if not keys or not keys[0]:
|
|
46
|
+
print(f"{Colors.ERR}Error: API Keys not found. Set GOOGLE_API_KEY environment variable or use --keys.{Colors.RESET}")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
session = SwarmSession(args, keys)
|
|
50
|
+
|
|
51
|
+
# 3. Handle loading (Arguments take priority after load)
|
|
52
|
+
if args.load and os.path.exists(args.load):
|
|
53
|
+
with open(args.load, 'r') as f:
|
|
54
|
+
sd = json.load(f)
|
|
55
|
+
session.current_agent_idx = sd.get("current_agent", 0)
|
|
56
|
+
session.last_interaction = sd.get("last_interaction", args.first_msg)
|
|
57
|
+
for i, ah in enumerate(sd.get("histories", [])):
|
|
58
|
+
if i < len(session.agents):
|
|
59
|
+
session.agents[i].history = Serializer.deserialize_history(ah)
|
|
60
|
+
print(f"{Colors.SYS}Session loaded. Overriding with current CLI parameters...{Colors.RESET}")
|
|
61
|
+
|
|
62
|
+
print(f"{Colors.SYS}{Colors.BOLD}>>> SWARM STARTING. CTRL+C FOR COMMAND CENTER.{Colors.RESET}")
|
|
63
|
+
|
|
64
|
+
while True:
|
|
65
|
+
try:
|
|
66
|
+
agent = session.agents[session.current_agent_idx]
|
|
67
|
+
color = Colors.AI_COLORS[session.current_agent_idx % len(Colors.AI_COLORS)]
|
|
68
|
+
|
|
69
|
+
# 1-Agent User Input Mode
|
|
70
|
+
if args.agents_count == 1 and len(agent.history) > 0:
|
|
71
|
+
user_msg = input(f"{Colors.BOLD}User: {Colors.RESET}")
|
|
72
|
+
session.last_interaction = user_msg
|
|
73
|
+
|
|
74
|
+
# Agent Thinking
|
|
75
|
+
response, reports = session.execute_react_step()
|
|
76
|
+
|
|
77
|
+
# Print and Share Reports
|
|
78
|
+
for rep in reports:
|
|
79
|
+
print(f"{Colors.REPORT}{rep}{Colors.RESET}")
|
|
80
|
+
for i, other in enumerate(session.agents):
|
|
81
|
+
if i != session.current_agent_idx:
|
|
82
|
+
other.history.append(types.Content(role="user", parts=[types.Part(text=rep)]))
|
|
83
|
+
|
|
84
|
+
# Final Output
|
|
85
|
+
print(f"{color}{Colors.BOLD}{agent.name}:{Colors.RESET} {response}")
|
|
86
|
+
|
|
87
|
+
# Turn Management
|
|
88
|
+
if not session.turn_passed_manually and args.agents_count > 1:
|
|
89
|
+
session.current_agent_idx = (session.current_agent_idx + 1) % args.agents_count
|
|
90
|
+
|
|
91
|
+
# Timing
|
|
92
|
+
wait = (len(response) / 10) * 1.5
|
|
93
|
+
smart_sleep(max(3, min(wait, 30)), enabled=session.enable_pauses)
|
|
94
|
+
|
|
95
|
+
except KeyboardInterrupt:
|
|
96
|
+
print(f"\n{Colors.MENU}{Colors.BOLD}=== SWARM COMMAND CENTER ==={Colors.RESET}")
|
|
97
|
+
print("1. Change Keys\n2. Toggle Pauses\n3. Save State\n4. Change Log Level\n5. Exit Swarm")
|
|
98
|
+
choice = input("Choice: ")
|
|
99
|
+
|
|
100
|
+
if choice == '1':
|
|
101
|
+
nk = input("Enter keys (space separated): ").split()
|
|
102
|
+
if nk: session.keys = nk; session.key_idx = 0
|
|
103
|
+
elif choice == '2':
|
|
104
|
+
session.enable_pauses = not session.enable_pauses
|
|
105
|
+
print(f"Pauses enabled: {session.enable_pauses}")
|
|
106
|
+
elif choice == '3':
|
|
107
|
+
path = input(f"Save filename [{args.save_file}]: ") or args.save_file
|
|
108
|
+
save_data = {
|
|
109
|
+
"current_agent": session.current_agent_idx,
|
|
110
|
+
"last_interaction": session.last_interaction,
|
|
111
|
+
"histories": [Serializer.serialize_history(a.history) for a in session.agents]
|
|
112
|
+
}
|
|
113
|
+
with open(path, 'w') as f: json.dump(save_data, f, indent=2)
|
|
114
|
+
print(f"State saved to {path}")
|
|
115
|
+
elif choice == '4':
|
|
116
|
+
lv = input("Enter Level (DEBUG, INFO, WARNING): ").upper()
|
|
117
|
+
logger.setLevel(getattr(logging, lv, logging.INFO))
|
|
118
|
+
elif choice == '5':
|
|
119
|
+
break
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from google import genai
|
|
3
|
+
from google.genai import types
|
|
4
|
+
from .utils import Colors, Serializer
|
|
5
|
+
from .tools import ToolRegistry
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("Swarm.Core")
|
|
8
|
+
|
|
9
|
+
class Agent:
|
|
10
|
+
"""Individual Agent state and configuration."""
|
|
11
|
+
def __init__(self, agent_id, name, description, config):
|
|
12
|
+
self.id = agent_id
|
|
13
|
+
self.name = name
|
|
14
|
+
self.description = description
|
|
15
|
+
self.model = config.get('model', 'gemini-3.1-flash-lite')
|
|
16
|
+
self.sys_prompt = config.get('sys_prompt', 'You are a helpful agent.')
|
|
17
|
+
self.temperature = config.get('temp', 0.7)
|
|
18
|
+
self.history = []
|
|
19
|
+
|
|
20
|
+
# Tool specifics for this agent
|
|
21
|
+
self.tools_enabled = config.get('tools', ["web_search", "shell_exec", "upload_file", "pass_turn"])
|
|
22
|
+
self.max_search = config.get('max_search', 5)
|
|
23
|
+
self.cmd_timeout = config.get('cmd_timeout', 300)
|
|
24
|
+
self.cmd_blacklist = config.get('cmd_blacklist', [])
|
|
25
|
+
self.file_blacklist = config.get('file_blacklist', [])
|
|
26
|
+
|
|
27
|
+
class SwarmSession:
|
|
28
|
+
"""Manager of the multi-agent ReAct loop."""
|
|
29
|
+
def __init__(self, args, keys):
|
|
30
|
+
self.args = args
|
|
31
|
+
self.keys = keys
|
|
32
|
+
self.key_idx = 0
|
|
33
|
+
self.client = genai.Client(api_key=self.keys[self.key_idx])
|
|
34
|
+
self.agents = []
|
|
35
|
+
self.current_agent_idx = 0
|
|
36
|
+
self.last_interaction = args.first_msg
|
|
37
|
+
self.enable_pauses = not args.no_pause
|
|
38
|
+
self.turn_passed_manually = False
|
|
39
|
+
self._init_agents(args)
|
|
40
|
+
|
|
41
|
+
def _init_agents(self, args):
|
|
42
|
+
for i in range(args.agents_count):
|
|
43
|
+
p = f"ai{i+1}_"
|
|
44
|
+
# Get values with fallbacks to global args
|
|
45
|
+
config = {
|
|
46
|
+
'model': getattr(args, f"{p}model", args.model),
|
|
47
|
+
'sys_prompt': getattr(args, f"{p}sys", args.sys1 if i == 0 else args.sys2),
|
|
48
|
+
'temp': getattr(args, f"{p}temp", args.temp),
|
|
49
|
+
'tools': getattr(args, f"{p}tools", ["web_search", "shell_exec", "upload_file", "pass_turn"]),
|
|
50
|
+
'max_search': getattr(args, f"{p}max_search", args.max_results),
|
|
51
|
+
'cmd_timeout': getattr(args, f"{p}cmd_timeout", args.cmd_timeout),
|
|
52
|
+
'cmd_blacklist': getattr(args, f"{p}cmd_blacklist", args.cmd_blacklist),
|
|
53
|
+
'file_blacklist': getattr(args, f"{p}file_blacklist", args.file_blacklist),
|
|
54
|
+
}
|
|
55
|
+
name = getattr(args, f"{p}name", f"Agent_{i+1}")
|
|
56
|
+
desc = getattr(args, f"{p}desc", "A Swarm Agent.")
|
|
57
|
+
self.agents.append(Agent(i+1, name, desc, config))
|
|
58
|
+
|
|
59
|
+
def rotate_key(self):
|
|
60
|
+
if len(self.keys) > 1:
|
|
61
|
+
self.key_idx = (self.key_idx + 1) % len(self.keys)
|
|
62
|
+
self.client = genai.Client(api_key=self.keys[self.key_idx])
|
|
63
|
+
logger.info(f"API key rotated to index {self.key_idx}")
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def build_system_instruction(self, agent):
|
|
68
|
+
"""Constructs the root prompt for the agent."""
|
|
69
|
+
swarm_map = "\n".join([f"- {a.name} (ID: {a.id}): {a.description}" for a in self.agents])
|
|
70
|
+
root = (
|
|
71
|
+
f"{agent.sys_prompt}\n\n"
|
|
72
|
+
f"FRAMEWORK: SWARM\n"
|
|
73
|
+
f"YOUR IDENTITY: {agent.name}\n"
|
|
74
|
+
f"ALL AGENTS IN SWARM:\n{swarm_map}\n\n"
|
|
75
|
+
f"GUIDELINES:\n"
|
|
76
|
+
f"1. You are autonomous. Use tools as needed.\n"
|
|
77
|
+
)
|
|
78
|
+
if len(self.agents) > 1:
|
|
79
|
+
root += "2. You can use 'pass_turn' to give control to another agent by their name.\n"
|
|
80
|
+
else:
|
|
81
|
+
root += "2. You are talking directly to the User.\n"
|
|
82
|
+
return root
|
|
83
|
+
|
|
84
|
+
def get_tool_definitions(self, agent):
|
|
85
|
+
decls = []
|
|
86
|
+
if "web_search" in agent.tools_enabled:
|
|
87
|
+
decls.append({"name": "web_search", "description": "Search the web.", "parameters": {"type": "OBJECT", "properties": {"query": {"type": "STRING"}}, "required": ["query"]}})
|
|
88
|
+
if "shell_exec" in agent.tools_enabled:
|
|
89
|
+
decls.append({"name": "shell_exec", "description": "Execute terminal commands.", "parameters": {"type": "OBJECT", "properties": {"command": {"type": "STRING"}}, "required": ["command"]}})
|
|
90
|
+
if "upload_file" in agent.tools_enabled:
|
|
91
|
+
decls.append({"name": "upload_file", "description": "Upload a local file for analysis.", "parameters": {"type": "OBJECT", "properties": {"path": {"type": "STRING"}}, "required": ["path"]}})
|
|
92
|
+
if "pass_turn" in agent.tools_enabled and len(self.agents) > 1:
|
|
93
|
+
decls.append({"name": "pass_turn", "description": "Transfer turn to another agent.", "parameters": {"type": "OBJECT", "properties": {"agent_name": {"type": "STRING"}}, "required": ["agent_name"]}})
|
|
94
|
+
return [{"function_declarations": decls}] if decls else None
|
|
95
|
+
|
|
96
|
+
def execute_react_step(self):
|
|
97
|
+
"""Runs the ReAct loop for the current agent."""
|
|
98
|
+
agent = self.agents[self.current_agent_idx]
|
|
99
|
+
agent.history.append(types.Content(role="user", parts=[types.Part(text=self.last_interaction)]))
|
|
100
|
+
|
|
101
|
+
full_text_output = ""
|
|
102
|
+
reports = []
|
|
103
|
+
self.turn_passed_manually = False
|
|
104
|
+
|
|
105
|
+
while True:
|
|
106
|
+
try:
|
|
107
|
+
config = types.GenerateContentConfig(
|
|
108
|
+
system_instruction=self.build_system_instruction(agent),
|
|
109
|
+
tools=self.get_tool_definitions(agent),
|
|
110
|
+
temperature=agent.temperature,
|
|
111
|
+
safety_settings=[types.SafetySetting(category=c, threshold="BLOCK_NONE") for c in [
|
|
112
|
+
"HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH",
|
|
113
|
+
"HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"]]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
response = self.client.models.generate_content(model=agent.model, config=config, contents=agent.history)
|
|
117
|
+
|
|
118
|
+
if not response.candidates:
|
|
119
|
+
return "[Blocked by Safety Filters]", []
|
|
120
|
+
|
|
121
|
+
candidate = response.candidates[0]
|
|
122
|
+
chunk_text = "".join([p.text for p in candidate.content.parts if p.text])
|
|
123
|
+
full_text_output += chunk_text
|
|
124
|
+
|
|
125
|
+
calls = [p.function_call for p in candidate.content.parts if p.function_call]
|
|
126
|
+
|
|
127
|
+
if not calls:
|
|
128
|
+
agent.history.append(candidate.content)
|
|
129
|
+
self.last_interaction = full_text_output
|
|
130
|
+
return full_text_output, reports
|
|
131
|
+
|
|
132
|
+
agent.history.append(candidate.content)
|
|
133
|
+
res_parts = []
|
|
134
|
+
|
|
135
|
+
for call in calls:
|
|
136
|
+
fn, args = call.name, call.args
|
|
137
|
+
logger.info(f"Agent {agent.name} calls tool: {fn}")
|
|
138
|
+
|
|
139
|
+
if fn == "pass_turn":
|
|
140
|
+
target = args.get("agent_name")
|
|
141
|
+
for i, a in enumerate(self.agents):
|
|
142
|
+
if a.name == target:
|
|
143
|
+
self.current_agent_idx = i
|
|
144
|
+
self.turn_passed_manually = True
|
|
145
|
+
res = f"Turn successfully passed to {target}."
|
|
146
|
+
break
|
|
147
|
+
else: res = f"Error: Agent '{target}' not found in Swarm."
|
|
148
|
+
elif fn == "web_search":
|
|
149
|
+
res = ToolRegistry.web_search(args.get("query"), max_results=agent.max_search)
|
|
150
|
+
elif fn == "shell_exec":
|
|
151
|
+
res = ToolRegistry.shell_exec(args.get("command"), timeout=agent.cmd_timeout, blacklist=agent.cmd_blacklist)
|
|
152
|
+
elif fn == "upload_file":
|
|
153
|
+
up = ToolRegistry.upload_file(self.client, args.get("path"), blacklist=agent.file_blacklist)
|
|
154
|
+
if "uri" in up:
|
|
155
|
+
res = f"File {args.get('path')} uploaded to {up['uri']}"
|
|
156
|
+
agent.history.append(types.Content(role="user", parts=[types.Part(file_data=types.FileData(file_uri=up['uri'], mime_type=up['mime']))]))
|
|
157
|
+
else: res = f"File Upload Error: {up.get('error')}"
|
|
158
|
+
|
|
159
|
+
res_parts.append(types.Part.from_function_response(name=fn, response={"result": res}))
|
|
160
|
+
reports.append(f"Tool Report: {agent.name} -> {fn}. Result: {str(res)[:150]}...")
|
|
161
|
+
|
|
162
|
+
agent.history.append(types.Content(role="tool", parts=res_parts))
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
if "429" in str(e) and self.rotate_key(): continue
|
|
166
|
+
logger.error(f"Cycle Exception: {e}")
|
|
167
|
+
return f"Error during agent cycle: {e}", []
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
import logging
|
|
6
|
+
from ddgs import DDGS
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("Swarm.Tools")
|
|
9
|
+
|
|
10
|
+
class ToolRegistry:
|
|
11
|
+
"""Static container for all tool logic with built-in security and limits."""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def web_search(query, max_results=5):
|
|
15
|
+
"""Standard web search tool."""
|
|
16
|
+
logger.debug(f"Searching for: {query} (limit: {max_results})")
|
|
17
|
+
try:
|
|
18
|
+
with DDGS() as ddgs:
|
|
19
|
+
results = [r for r in ddgs.text(query, max_results=max_results)]
|
|
20
|
+
if not results:
|
|
21
|
+
return "No search results found."
|
|
22
|
+
return json.dumps(results, ensure_ascii=False, indent=2)
|
|
23
|
+
except Exception as e:
|
|
24
|
+
logger.error(f"DDGS Error: {e}")
|
|
25
|
+
return f"Search failed: {str(e)}"
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def shell_exec(command, timeout=300, blacklist=None):
|
|
29
|
+
"""Secure shell execution tool."""
|
|
30
|
+
logger.debug(f"Executing: {command} (timeout: {timeout})")
|
|
31
|
+
if blacklist:
|
|
32
|
+
for pattern in blacklist:
|
|
33
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
34
|
+
logger.warning(f"Blocked command: {command} (matches {pattern})")
|
|
35
|
+
return f"Security Exception: Command matches blacklist pattern '{pattern}'."
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
res = subprocess.run(
|
|
39
|
+
command, shell=True, capture_output=True, text=True, timeout=timeout
|
|
40
|
+
)
|
|
41
|
+
output = f"STDOUT:\n{res.stdout}\nSTDERR:\n{res.stderr}"
|
|
42
|
+
return output if output.strip() else "Executed successfully with no output."
|
|
43
|
+
except subprocess.TimeoutExpired:
|
|
44
|
+
return f"Timeout Error: Command exceeded {timeout} seconds."
|
|
45
|
+
except Exception as e:
|
|
46
|
+
return f"Execution failed: {str(e)}"
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def upload_file(client, path, blacklist=None):
|
|
50
|
+
"""Google Files API wrapper."""
|
|
51
|
+
if not os.path.exists(path):
|
|
52
|
+
return {"error": f"Path '{path}' does not exist."}
|
|
53
|
+
|
|
54
|
+
if blacklist:
|
|
55
|
+
for pattern in blacklist:
|
|
56
|
+
if re.search(pattern, path, re.IGNORECASE):
|
|
57
|
+
return {"error": f"Access to '{path}' is denied by security policy."}
|
|
58
|
+
|
|
59
|
+
logger.info(f"Uploading file: {path}")
|
|
60
|
+
try:
|
|
61
|
+
file_obj = client.files.upload(path=path)
|
|
62
|
+
while file_obj.state.name == "PROCESSING":
|
|
63
|
+
time.sleep(2)
|
|
64
|
+
file_obj = client.files.get(name=file_obj.name)
|
|
65
|
+
|
|
66
|
+
if file_obj.state.name == "FAILED":
|
|
67
|
+
return {"error": "Server-side file processing failed."}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
"uri": file_obj.uri,
|
|
71
|
+
"mime": file_obj.mime_type,
|
|
72
|
+
"name": file_obj.name
|
|
73
|
+
}
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Upload error: {e}")
|
|
76
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import select
|
|
6
|
+
import logging
|
|
7
|
+
from google.genai import types
|
|
8
|
+
|
|
9
|
+
class Colors:
|
|
10
|
+
"""ANSI Escape sequences for professional terminal output."""
|
|
11
|
+
AI_COLORS = ['\033[94m', '\033[92m', '\033[96m', '\033[95m', '\033[91m', '\033[33m']
|
|
12
|
+
SYS = '\033[93m'
|
|
13
|
+
ERR = '\033[91m'
|
|
14
|
+
TOOL = '\033[36m'
|
|
15
|
+
REPORT = '\033[90m'
|
|
16
|
+
MENU = '\033[95m'
|
|
17
|
+
BOLD = '\033[1m'
|
|
18
|
+
RESET = '\033[0m'
|
|
19
|
+
|
|
20
|
+
def setup_logger(level_name="INFO"):
|
|
21
|
+
"""Initializes the logging system with configurable levels."""
|
|
22
|
+
level = getattr(logging, level_name.upper(), logging.INFO)
|
|
23
|
+
logging.basicConfig(
|
|
24
|
+
level=level,
|
|
25
|
+
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
|
|
26
|
+
handlers=[logging.StreamHandler(sys.stdout)]
|
|
27
|
+
)
|
|
28
|
+
return logging.getLogger("Swarm")
|
|
29
|
+
|
|
30
|
+
def smart_sleep(timeout, enabled=True):
|
|
31
|
+
"""Provides a toggleable pause that can be skipped with the Enter key."""
|
|
32
|
+
if not enabled or timeout <= 0:
|
|
33
|
+
return
|
|
34
|
+
print(f"{Colors.REPORT}[Pause: {timeout:.1f}s | Press Enter to skip]{Colors.RESET}", end="", flush=True)
|
|
35
|
+
# Use select for non-blocking stdin check
|
|
36
|
+
ready, _, _ = select.select([sys.stdin], [], [], timeout)
|
|
37
|
+
if ready:
|
|
38
|
+
sys.stdin.readline()
|
|
39
|
+
print(f"\r{Colors.SYS}[User Skipped Pause]{' ' * 30}{Colors.RESET}")
|
|
40
|
+
else:
|
|
41
|
+
# Clear the line after pause expires
|
|
42
|
+
print(f"\r{' ' * 50}\r", end="", flush=True)
|
|
43
|
+
|
|
44
|
+
class Serializer:
|
|
45
|
+
"""Serializes and deserializes Google GenAI objects for persistent storage."""
|
|
46
|
+
@staticmethod
|
|
47
|
+
def serialize_history(history):
|
|
48
|
+
data = []
|
|
49
|
+
for content in history:
|
|
50
|
+
parts_list = []
|
|
51
|
+
for p in content.parts:
|
|
52
|
+
if p.text:
|
|
53
|
+
parts_list.append({"text": p.text})
|
|
54
|
+
elif p.function_call:
|
|
55
|
+
parts_list.append({"function_call": {"name": p.function_call.name, "args": p.function_call.args}})
|
|
56
|
+
elif p.function_response:
|
|
57
|
+
parts_list.append({"function_response": {"name": p.function_response.name, "response": p.function_response.response}})
|
|
58
|
+
elif p.file_data:
|
|
59
|
+
parts_list.append({"file_data": {"file_uri": p.file_data.file_uri, "mime_type": p.file_data.mime_type}})
|
|
60
|
+
data.append({"role": content.role, "parts": parts_list})
|
|
61
|
+
return data
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def deserialize_history(data):
|
|
65
|
+
history = []
|
|
66
|
+
for item in data:
|
|
67
|
+
parts = []
|
|
68
|
+
for p in item.get('parts', []):
|
|
69
|
+
if "text" in p:
|
|
70
|
+
parts.append(types.Part(text=p["text"]))
|
|
71
|
+
elif "function_call" in p:
|
|
72
|
+
fc = p["function_call"]
|
|
73
|
+
parts.append(types.Part(function_call=types.FunctionCall(name=fc["name"], args=fc["args"])))
|
|
74
|
+
elif "function_response" in p:
|
|
75
|
+
fr = p["function_response"]
|
|
76
|
+
parts.append(types.Part.from_function_response(name=fr["name"], response=fr["response"]))
|
|
77
|
+
elif "file_data" in p:
|
|
78
|
+
fd = p["file_data"]
|
|
79
|
+
parts.append(types.Part(file_data=types.FileData(file_uri=fd["file_uri"], mime_type=fd["mime_type"])))
|
|
80
|
+
if parts:
|
|
81
|
+
history.append(types.Content(role=item["role"], parts=parts))
|
|
82
|
+
return history
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swarmai
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A highly flexible multi-agent ReAct framework powered by Google Gemini
|
|
5
|
+
Author-email: ProgVM <progvminc@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/ProgVM
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: google-genai
|
|
13
|
+
Requires-Dist: duckduckgo-search
|
|
14
|
+
|
|
15
|
+
# 🐝 Swarm
|
|
16
|
+
|
|
17
|
+
**Swarm** is an advanced, open-source multi-agent intelligence framework designed for autonomous collaboration. Powered by Google's **Gemini 3.1 Flash Lite**, Swarm allows multiple agents to interact, use tools, execute code, and perform web searches in a shared environment.
|
|
18
|
+
|
|
19
|
+
## 🚀 Key Features
|
|
20
|
+
|
|
21
|
+
- **N-Agent Collaboration**: Run any number of agents in a single session.
|
|
22
|
+
- **Autonomous Tool Usage**: Agents can perform Google searches and execute Shell commands.
|
|
23
|
+
- **Agent Sandboxing**: Per-agent blacklists for terminal commands and file paths.
|
|
24
|
+
- **Turn Management**: Agents can autonomously pass turns to specific peers using `pass_turn`.
|
|
25
|
+
- **ReAct Loop**: High-level reasoning before acting, supporting multi-step tool execution.
|
|
26
|
+
- **Files API Support**: Agents can upload and analyze PDF, images, and logs via Google Files API.
|
|
27
|
+
- **Persistent Sessions**: Save and load full agent histories and environment states.
|
|
28
|
+
|
|
29
|
+
## 🛠 Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Clone the repository
|
|
33
|
+
git clone https://github.com/ProgVM/swarm
|
|
34
|
+
cd swarm
|
|
35
|
+
|
|
36
|
+
# Install dependencies
|
|
37
|
+
pip install .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or:
|
|
41
|
+
```bash
|
|
42
|
+
pip install swarmai
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 💻 CLI Usage
|
|
46
|
+
|
|
47
|
+
Start a basic conversation with two agents:
|
|
48
|
+
```bash
|
|
49
|
+
swarm --keys YOUR_API_KEY --agents_count 2
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Start an autonomous coding session:
|
|
53
|
+
```bash
|
|
54
|
+
swarm --first_msg "Write a web scraper in Python and test it" \
|
|
55
|
+
--sys1 "You are an Expert Coder" \
|
|
56
|
+
--sys2 "You are a Security Auditor" \
|
|
57
|
+
--cmd_timeout 600
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Advanced Configuration
|
|
61
|
+
Swarm supports individual parameters for every agent using `aiN_` prefixes:
|
|
62
|
+
- `--ai1_name "Architect"`
|
|
63
|
+
- `--ai1_model "gemini-2.0-flash"`
|
|
64
|
+
- `--ai2_name "Tester"`
|
|
65
|
+
- `--ai2_temp 0.2`
|
|
66
|
+
|
|
67
|
+
## ⚙️ Configuration File
|
|
68
|
+
You can use a JSON config to manage complex Swarms:
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"agents_count": 3,
|
|
72
|
+
"model": "gemini-3.1-flash-lite",
|
|
73
|
+
"cmd_blacklist": ["rm -rf", "format"],
|
|
74
|
+
"no_pause": true
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
Run with: `swarm --config my_swarm.json`
|
|
78
|
+
|
|
79
|
+
## ⌨️ Command Center (Interactive Menu)
|
|
80
|
+
Press `Ctrl+C` at any time to:
|
|
81
|
+
- Rotate API Keys.
|
|
82
|
+
- Toggle reading pauses.
|
|
83
|
+
- Save the current state.
|
|
84
|
+
- Change logging levels (DEBUG, INFO, WARNING).
|
|
85
|
+
- Inject manual directives into agents' minds.
|
|
86
|
+
|
|
87
|
+
## 🤝 Contributing
|
|
88
|
+
Developed by **ProgVM**.
|
|
89
|
+
- Email: progvminc@gmail.com
|
|
90
|
+
- GitHub: [https://github.com/ProgVM](https://github.com/ProgVM)
|
|
91
|
+
|
|
92
|
+
License: MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
swarm/__init__.py
|
|
4
|
+
swarm/cli.py
|
|
5
|
+
swarm/core.py
|
|
6
|
+
swarm/tools.py
|
|
7
|
+
swarm/utils.py
|
|
8
|
+
swarmai.egg-info/PKG-INFO
|
|
9
|
+
swarmai.egg-info/SOURCES.txt
|
|
10
|
+
swarmai.egg-info/dependency_links.txt
|
|
11
|
+
swarmai.egg-info/entry_points.txt
|
|
12
|
+
swarmai.egg-info/requires.txt
|
|
13
|
+
swarmai.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
swarm
|