solana-agent 4.0.3__py3-none-any.whl → 6.0.0__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.
solana_agent/ai.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import datetime
3
+ import traceback
3
4
  import ntplib
4
5
  import json
5
6
  from typing import AsyncGenerator, List, Literal, Dict, Any, Callable
@@ -1106,4 +1107,292 @@ class AI:
1106
1107
  return func
1107
1108
 
1108
1109
 
1109
- tool = AI.add_tool
1110
+ class Swarm:
1111
+ """An AI Agent Swarm that coordinates specialized AI agents with handoff capabilities."""
1112
+
1113
+ def __init__(self, database: MongoDatabase, router_model: str = "gpt-4o"):
1114
+ """Initialize the multi-agent system with a shared database.
1115
+
1116
+ Args:
1117
+ database (MongoDatabase): Shared MongoDB database instance
1118
+ router_model (str, optional): Model to use for routing decisions. Defaults to "gpt-4o".
1119
+ """
1120
+ self.agents = {} # name -> AI instance
1121
+ self.specializations = {} # name -> description
1122
+ self.database = database
1123
+ self.router_model = router_model
1124
+
1125
+ # Ensure handoffs collection exists
1126
+ if "handoffs" not in self.database.db.list_collection_names():
1127
+ self.database.db.create_collection("handoffs")
1128
+ self.handoffs = self.database.db["handoffs"]
1129
+
1130
+ print(
1131
+ f"MultiAgentSystem initialized with router model: {router_model}")
1132
+
1133
+ def register(self, name: str, agent: AI, specialization: str):
1134
+ """Register a specialized agent with the multi-agent system."""
1135
+ # Add the agent to the system first
1136
+ self.agents[name] = agent
1137
+ self.specializations[name] = specialization
1138
+
1139
+ print(
1140
+ f"Registered agent: {name}, specialization: {specialization[:50]}...")
1141
+ print(f"Current agents: {list(self.agents.keys())}")
1142
+
1143
+ # We need to refresh handoff tools for ALL agents whenever a new one is registered
1144
+ self._update_all_agent_tools()
1145
+
1146
+ def _update_all_agent_tools(self):
1147
+ """Update all agents with current handoff capabilities."""
1148
+ # For each registered agent, update its handoff tool
1149
+ for agent_name, agent in self.agents.items():
1150
+ # Get other agents that this agent can hand off to
1151
+ available_targets = [
1152
+ name for name in self.agents.keys() if name != agent_name
1153
+ ]
1154
+
1155
+ # First remove any existing handoff tool if present
1156
+ agent._tools = [
1157
+ t for t in agent._tools if t["function"]["name"] != "request_handoff"
1158
+ ]
1159
+
1160
+ # Create handoff tool with explicit naming requirements
1161
+ def create_handoff_tool(current_agent_name, available_targets_list):
1162
+ def request_handoff(target_agent: str, reason: str) -> str:
1163
+ """Request an immediate handoff to another specialized agent.
1164
+ This is an INTERNAL SYSTEM TOOL. The user will NOT see your reasoning about the handoff.
1165
+ Use this tool IMMEDIATELY when a query is outside your expertise.
1166
+
1167
+ Args:
1168
+ target_agent (str): Name of agent to transfer to. MUST be one of: {', '.join(available_targets_list)}.
1169
+ DO NOT INVENT NEW NAMES OR VARIATIONS. Use EXACTLY one of these names.
1170
+ reason (str): Brief explanation of why this question requires the specialist
1171
+
1172
+ Returns:
1173
+ str: Empty string - the handoff is handled internally
1174
+ """
1175
+ # Prevent self-handoffs
1176
+ if target_agent == current_agent_name:
1177
+ print(
1178
+ f"[HANDOFF ERROR] Agent {current_agent_name} attempted to hand off to itself"
1179
+ )
1180
+ if available_targets_list:
1181
+ target_agent = available_targets_list[0]
1182
+ print(
1183
+ f"[HANDOFF CORRECTION] Redirecting to {target_agent} instead"
1184
+ )
1185
+ else:
1186
+ print(
1187
+ "[HANDOFF ERROR] No other agents available to hand off to"
1188
+ )
1189
+ return ""
1190
+
1191
+ # Validate target agent exists
1192
+ if target_agent not in self.agents:
1193
+ print(
1194
+ f"[HANDOFF WARNING] Invalid target '{target_agent}'")
1195
+ if available_targets_list:
1196
+ original_target = target_agent
1197
+ target_agent = available_targets_list[0]
1198
+ print(
1199
+ f"[HANDOFF CORRECTION] Redirecting from '{original_target}' to '{target_agent}'"
1200
+ )
1201
+ else:
1202
+ print(
1203
+ "[HANDOFF ERROR] No valid target agents available")
1204
+ return ""
1205
+
1206
+ print(
1207
+ f"[HANDOFF TOOL CALLED] {current_agent_name} -> {target_agent}: {reason}"
1208
+ )
1209
+
1210
+ # Set handoff info in the agent instance for processing
1211
+ agent._handoff_info = {
1212
+ "target": target_agent, "reason": reason}
1213
+
1214
+ # Return empty string - the actual handoff happens in the process method
1215
+ return ""
1216
+
1217
+ return request_handoff
1218
+
1219
+ # Use the factory to create a properly-bound tool function
1220
+ handoff_tool = create_handoff_tool(agent_name, available_targets)
1221
+
1222
+ # Initialize handoff info attribute
1223
+ agent._handoff_info = None
1224
+
1225
+ # Add the updated handoff tool with proper closure
1226
+ agent.add_tool(handoff_tool)
1227
+
1228
+ # Add critical handoff instructions to the agent's base instructions
1229
+ handoff_examples = "\n".join(
1230
+ [
1231
+ f" - `{name}` ({self.specializations[name][:40]}...)"
1232
+ for name in available_targets
1233
+ ]
1234
+ )
1235
+ handoff_instructions = f"""
1236
+ STRICT HANDOFF GUIDANCE:
1237
+ 1. You must use ONLY the EXACT agent names listed below for handoffs:
1238
+ {handoff_examples}
1239
+
1240
+ 2. DO NOT INVENT, MODIFY, OR CREATE NEW AGENT NAMES like "Smart Contract Developer" or "Technical Expert"
1241
+
1242
+ 3. For technical implementation questions, use "developer" (not variations like "developer expert" or "tech specialist")
1243
+
1244
+ 4. ONLY these EXACT agent names will work for handoffs: {', '.join(available_targets)}
1245
+ """
1246
+
1247
+ # Update agent instructions with handoff guidance
1248
+ agent._instructions = agent._instructions + "\n\n" + handoff_instructions
1249
+
1250
+ print(
1251
+ f"Updated handoff capabilities for {agent_name} with targets: {available_targets}"
1252
+ )
1253
+
1254
+ async def process(self, user_id: str, user_text: str) -> AsyncGenerator[str, None]:
1255
+ """Process the user request with appropriate agent and handle handoffs."""
1256
+ try:
1257
+ # Check if any agents are registered
1258
+ if not self.agents:
1259
+ yield "Error: No agents are registered with the system. Please register at least one agent first."
1260
+ return
1261
+
1262
+ # Get routing decision
1263
+ first_agent = next(iter(self.agents.values()))
1264
+ agent_name = await self._get_routing_decision(first_agent, user_text)
1265
+ current_agent = self.agents[agent_name]
1266
+ print(f"Starting conversation with agent: {agent_name}")
1267
+
1268
+ # Initialize a flag for handoff detection
1269
+ handoff_detected = False
1270
+ response_started = False
1271
+
1272
+ # Reset handoff info for this interaction
1273
+ current_agent._handoff_info = None
1274
+
1275
+ # Process initial agent's response
1276
+ async for chunk in current_agent.text(user_id, user_text):
1277
+ # Check for handoff after each chunk
1278
+ if current_agent._handoff_info and not handoff_detected:
1279
+ handoff_detected = True
1280
+ target_name = current_agent._handoff_info["target"]
1281
+ target_agent = self.agents[target_name]
1282
+ reason = current_agent._handoff_info["reason"]
1283
+
1284
+ # Record handoff without waiting
1285
+ asyncio.create_task(
1286
+ self._record_handoff(
1287
+ user_id, agent_name, target_name, reason, user_text
1288
+ )
1289
+ )
1290
+
1291
+ # Process with target agent
1292
+ print(f"[HANDOFF] Forwarding to {target_name}")
1293
+ handoff_query = f"""
1294
+ Answer this ENTIRE question completely from scratch:
1295
+
1296
+ {user_text}
1297
+
1298
+ IMPORTANT INSTRUCTIONS:
1299
+ 1. Address ALL aspects of the question comprehensively
1300
+ 2. Organize your response in a logical, structured manner
1301
+ 3. Include both explanations AND implementations as needed
1302
+ 4. Do not mention any handoff or that you're continuing from another agent
1303
+ 5. Answer as if you are addressing the complete question from the beginning
1304
+ 6. Consider any relevant context from previous conversation
1305
+ """
1306
+
1307
+ # If we've already started returning some text, add a separator
1308
+ if response_started:
1309
+ yield "\n\n---\n\n"
1310
+
1311
+ # Stream directly from target agent
1312
+ async for new_chunk in target_agent.text(user_id, handoff_query):
1313
+ yield new_chunk
1314
+ # Force immediate delivery of each chunk
1315
+ await asyncio.sleep(0)
1316
+ return
1317
+ else:
1318
+ # Only yield content if no handoff has been detected
1319
+ if not handoff_detected:
1320
+ response_started = True
1321
+ yield chunk
1322
+ await asyncio.sleep(0) # Force immediate delivery
1323
+
1324
+ except Exception as e:
1325
+ print(f"Error in multi-agent processing: {str(e)}")
1326
+ print(traceback.format_exc())
1327
+ yield "\n\nI apologize for the technical difficulty.\n\n"
1328
+
1329
+ async def _get_routing_decision(self, agent, user_text):
1330
+ """Get routing decision in parallel to reduce latency."""
1331
+ enhanced_prompt = f"""
1332
+ Analyze this user query carefully to determine the MOST APPROPRIATE specialist.
1333
+
1334
+ User query: "{user_text}"
1335
+
1336
+ Available specialists:
1337
+ {json.dumps(self.specializations, indent=2)}
1338
+
1339
+ CRITICAL ROUTING INSTRUCTIONS:
1340
+ 1. For compound questions with multiple aspects spanning different domains,
1341
+ choose the specialist who should address the CONCEPTUAL or EDUCATIONAL aspects first.
1342
+
1343
+ 2. Choose implementation specialists (technical, development, coding) only when
1344
+ the query is PURELY about implementation with no conceptual explanation needed.
1345
+
1346
+ 3. When a query involves a SEQUENCE (like "explain X and then do Y"),
1347
+ prioritize the specialist handling the FIRST part of the sequence.
1348
+
1349
+ Return ONLY the name of the single most appropriate specialist.
1350
+ """
1351
+
1352
+ # Route to appropriate agent
1353
+ router_response = agent._client.chat.completions.create(
1354
+ model=self.router_model,
1355
+ messages=[
1356
+ {"role": "system", "content": enhanced_prompt},
1357
+ {"role": "user", "content": user_text},
1358
+ ],
1359
+ temperature=0.2,
1360
+ )
1361
+
1362
+ # Extract the selected agent
1363
+ raw_response = router_response.choices[0].message.content.strip()
1364
+ print(f"Router model raw response: '{raw_response}'")
1365
+
1366
+ return self._match_agent_name(raw_response)
1367
+
1368
+ async def _record_handoff(self, user_id, from_agent, to_agent, reason, query):
1369
+ """Record handoff in database without blocking the main flow."""
1370
+ self.handoffs.insert_one(
1371
+ {
1372
+ "user_id": user_id,
1373
+ "from_agent": from_agent,
1374
+ "to_agent": to_agent,
1375
+ "reason": reason,
1376
+ "query": query,
1377
+ "timestamp": datetime.datetime.now(datetime.timezone.utc),
1378
+ }
1379
+ )
1380
+
1381
+ def _match_agent_name(self, raw_response):
1382
+ """Match router response to an actual agent name."""
1383
+ # Exact match (priority)
1384
+ if raw_response in self.agents:
1385
+ return raw_response
1386
+
1387
+ # Case-insensitive match
1388
+ for name in self.agents:
1389
+ if name.lower() == raw_response.lower():
1390
+ return name
1391
+
1392
+ # Partial match
1393
+ for name in self.agents:
1394
+ if name.lower() in raw_response.lower():
1395
+ return name
1396
+
1397
+ # Fallback to first agent
1398
+ return list(self.agents.keys())[0]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solana-agent
3
- Version: 4.0.3
3
+ Version: 6.0.0
4
4
  Summary: Build self-learning AI Agents
5
5
  License: MIT
6
6
  Keywords: ai,openai,ai agents
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
18
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
19
  Requires-Dist: ntplib (>=0.4.0,<0.5.0)
20
- Requires-Dist: openai (>=1.64.0,<2.0.0)
20
+ Requires-Dist: openai (>=1.65.1,<2.0.0)
21
21
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
22
22
  Requires-Dist: pinecone (>=6.0.1,<7.0.0)
23
23
  Requires-Dist: pydantic (>=2.10.6,<3.0.0)
@@ -62,6 +62,12 @@ Unlike traditional AI assistants that forget conversations after each session, S
62
62
  - Advanced AI memory combining conversational context, knowledge base, and parallel tool calling
63
63
  - Create custom tools for extending the Agent's capabilities like further API integrations
64
64
 
65
+ 🤖 **Multi-Agent Swarms**
66
+ - Create specialized agents with different expertise domains
67
+ - Automatic routing of queries to the most appropriate specialist
68
+ - Seamless handoffs between agents for complex multi-domain questions
69
+ - Shared memory and context across the entire agent swarm
70
+
65
71
  🔍 **Multi-Source Search and Reasoning**
66
72
  - Internet search via Perplexity
67
73
  - X (Twitter) search using Grok
@@ -82,6 +88,13 @@ Unlike traditional AI assistants that forget conversations after each session, S
82
88
  - Knowledge Base to add domain specific knowledge
83
89
  - CSV file uploads to perform document context search
84
90
 
91
+ 🤝 **Intelligent Multi-Agent Systems**
92
+ - First-class support for specialized agent swarms
93
+ - Dynamic inter-agent routing based on query complexity
94
+ - Seamless handoffs with continuous memory preservation
95
+ - Single unified interface for the entire agent network
96
+ - No custom coding required for agent coordination
97
+
85
98
  🏢 **Enterprise Focus**
86
99
  - Production-ready out of the box in a few lines of code
87
100
  - Enterprise-grade deployment options for all components and services
@@ -0,0 +1,6 @@
1
+ solana_agent/__init__.py,sha256=zpfnWqANd3OHGWm7NCF5Y6m01BWG4NkNk8SK9Ex48nA,18
2
+ solana_agent/ai.py,sha256=zwxvAOBhmdzUr9KLeTCsRW1Kg8LpIxJiu0PtxhQj-cI,57017
3
+ solana_agent-6.0.0.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
4
+ solana_agent-6.0.0.dist-info/METADATA,sha256=y9c0cPhj3x4hzr4qRSNAJIdCVoxhmhlsoux_3fiBLDQ,5419
5
+ solana_agent-6.0.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
6
+ solana_agent-6.0.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- solana_agent/__init__.py,sha256=zpfnWqANd3OHGWm7NCF5Y6m01BWG4NkNk8SK9Ex48nA,18
2
- solana_agent/ai.py,sha256=t1BJtTZ7RHzUL_5DNCrJHBGxV6xWk2-x5fhsO0pM6p8,44136
3
- solana_agent-4.0.3.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
4
- solana_agent-4.0.3.dist-info/METADATA,sha256=rguDaFGlywlBNdYHLjWjJzER1fXFrTis0lFPNXIIu7I,4808
5
- solana_agent-4.0.3.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
6
- solana_agent-4.0.3.dist-info/RECORD,,