solana-agent 19.1.0__py3-none-any.whl → 20.1.4__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/client/solana_agent.py +14 -21
- solana_agent/factories/agent_factory.py +13 -16
- solana_agent/interfaces/services/routing.py +1 -2
- solana_agent/plugins/manager.py +29 -30
- solana_agent/plugins/registry.py +20 -3
- solana_agent/plugins/tools/auto_tool.py +2 -0
- solana_agent/repositories/memory.py +95 -50
- solana_agent/services/agent.py +5 -3
- solana_agent/services/query.py +8 -1
- solana_agent/services/routing.py +1 -1
- {solana_agent-19.1.0.dist-info → solana_agent-20.1.4.dist-info}/METADATA +242 -60
- {solana_agent-19.1.0.dist-info → solana_agent-20.1.4.dist-info}/RECORD +14 -14
- {solana_agent-19.1.0.dist-info → solana_agent-20.1.4.dist-info}/LICENSE +0 -0
- {solana_agent-19.1.0.dist-info → solana_agent-20.1.4.dist-info}/WHEEL +0 -0
@@ -11,6 +11,7 @@ from typing import AsyncGenerator, Dict, Any, Literal, Optional, Union
|
|
11
11
|
from solana_agent.factories.agent_factory import SolanaAgentFactory
|
12
12
|
from solana_agent.interfaces.client.client import SolanaAgent as SolanaAgentInterface
|
13
13
|
from solana_agent.interfaces.plugins.plugins import Tool
|
14
|
+
from solana_agent.interfaces.services.routing import RoutingService as RoutingInterface
|
14
15
|
|
15
16
|
|
16
17
|
class SolanaAgent(SolanaAgentInterface):
|
@@ -54,6 +55,7 @@ class SolanaAgent(SolanaAgentInterface):
|
|
54
55
|
audio_input_format: Literal[
|
55
56
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
56
57
|
] = "mp4",
|
58
|
+
router: Optional[RoutingInterface] = None,
|
57
59
|
) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
|
58
60
|
"""Process a user message and return the response stream.
|
59
61
|
|
@@ -63,9 +65,10 @@ class SolanaAgent(SolanaAgentInterface):
|
|
63
65
|
prompt: Optional prompt for the agent
|
64
66
|
output_format: Response format ("text" or "audio")
|
65
67
|
audio_voice: Voice to use for audio output
|
66
|
-
audio_instructions:
|
68
|
+
audio_instructions: Not used currently
|
67
69
|
audio_output_format: Audio output format
|
68
70
|
audio_input_format: Audio input format
|
71
|
+
router: Optional routing service for processing
|
69
72
|
|
70
73
|
Returns:
|
71
74
|
Async generator yielding response chunks (text strings or audio bytes)
|
@@ -79,6 +82,7 @@ class SolanaAgent(SolanaAgentInterface):
|
|
79
82
|
audio_output_format=audio_output_format,
|
80
83
|
audio_input_format=audio_input_format,
|
81
84
|
prompt=prompt,
|
85
|
+
router=router,
|
82
86
|
):
|
83
87
|
yield chunk
|
84
88
|
|
@@ -97,7 +101,7 @@ class SolanaAgent(SolanaAgentInterface):
|
|
97
101
|
page_num: int = 1,
|
98
102
|
page_size: int = 20,
|
99
103
|
sort_order: str = "desc" # "asc" for oldest-first, "desc" for newest-first
|
100
|
-
) -> Dict[str, Any]:
|
104
|
+
) -> Dict[str, Any]: # pragma: no cover
|
101
105
|
"""
|
102
106
|
Get paginated message history for a user.
|
103
107
|
|
@@ -124,22 +128,11 @@ class SolanaAgent(SolanaAgentInterface):
|
|
124
128
|
Returns:
|
125
129
|
True if successful, False
|
126
130
|
"""
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
agents = self.query_service.agent_service.get_all_ai_agents()
|
136
|
-
for agent_name in agents:
|
137
|
-
print(f"Assigning {tool.name} to agent {agent_name}")
|
138
|
-
self.query_service.agent_service.assign_tool_for_agent(
|
139
|
-
agent_name, tool.name)
|
140
|
-
return success
|
141
|
-
except Exception as e:
|
142
|
-
print(f"Error in register_tool: {str(e)}")
|
143
|
-
import traceback
|
144
|
-
print(traceback.format_exc())
|
145
|
-
return False
|
131
|
+
success = self.query_service.agent_service.tool_registry.register_tool(
|
132
|
+
tool)
|
133
|
+
if success:
|
134
|
+
agents = self.query_service.agent_service.get_all_ai_agents()
|
135
|
+
for agent_name in agents:
|
136
|
+
self.query_service.agent_service.assign_tool_for_agent(
|
137
|
+
agent_name, tool.name)
|
138
|
+
return success
|
@@ -109,8 +109,12 @@ class SolanaAgentFactory:
|
|
109
109
|
config=config,
|
110
110
|
tool_registry=agent_service.tool_registry
|
111
111
|
)
|
112
|
-
|
113
|
-
|
112
|
+
try:
|
113
|
+
loaded_plugins = agent_service.plugin_manager.load_plugins()
|
114
|
+
print(f"Loaded {loaded_plugins} plugins")
|
115
|
+
except Exception as e:
|
116
|
+
print(f"Error loading plugins: {e}")
|
117
|
+
loaded_plugins = 0
|
114
118
|
|
115
119
|
# Register predefined agents
|
116
120
|
for agent_config in config.get("agents", []):
|
@@ -125,25 +129,18 @@ class SolanaAgentFactory:
|
|
125
129
|
for tool_name in agent_config["tools"]:
|
126
130
|
print(
|
127
131
|
f"Available tools before registering {tool_name}: {agent_service.tool_registry.list_all_tools()}")
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
f"Successfully registered {tool_name} for agent {agent_config['name']}")
|
134
|
-
except ValueError as e:
|
135
|
-
print(
|
136
|
-
f"Error registering tool {tool_name} for agent {agent_config['name']}: {e}")
|
132
|
+
agent_service.assign_tool_for_agent(
|
133
|
+
agent_config["name"], tool_name
|
134
|
+
)
|
135
|
+
print(
|
136
|
+
f"Successfully registered {tool_name} for agent {agent_config['name']}")
|
137
137
|
|
138
138
|
# Global tool registrations
|
139
139
|
if "agent_tools" in config:
|
140
140
|
for agent_name, tools in config["agent_tools"].items():
|
141
141
|
for tool_name in tools:
|
142
|
-
|
143
|
-
|
144
|
-
agent_name, tool_name)
|
145
|
-
except ValueError as e:
|
146
|
-
print(f"Error registering tool: {e}")
|
142
|
+
agent_service.assign_tool_for_agent(
|
143
|
+
agent_name, tool_name)
|
147
144
|
|
148
145
|
# Create and return the query service
|
149
146
|
query_service = QueryService(
|
@@ -6,11 +6,10 @@ class RoutingService(ABC):
|
|
6
6
|
"""Interface for query routing services."""
|
7
7
|
|
8
8
|
@abstractmethod
|
9
|
-
async def route_query(self, query: str) ->
|
9
|
+
async def route_query(self, query: str) -> str:
|
10
10
|
"""Route a query to the appropriate agent.
|
11
11
|
|
12
12
|
Args:
|
13
|
-
user_id: User ID
|
14
13
|
query: User query
|
15
14
|
|
16
15
|
Returns:
|
solana_agent/plugins/manager.py
CHANGED
@@ -35,19 +35,21 @@ class PluginManager(PluginManagerInterface):
|
|
35
35
|
True if registration succeeded, False otherwise
|
36
36
|
"""
|
37
37
|
try:
|
38
|
-
#
|
39
|
-
self._plugins[plugin.name] = plugin
|
40
|
-
|
41
|
-
# Initialize the plugin with the tool registry
|
38
|
+
# Initialize the plugin with the tool registry first
|
42
39
|
plugin.initialize(self.tool_registry)
|
43
40
|
|
44
|
-
#
|
45
|
-
print(f"Configuring plugin {plugin.name} with config")
|
41
|
+
# Then configure the plugin
|
46
42
|
plugin.configure(self.config)
|
47
43
|
|
44
|
+
# Only store plugin if both initialize and configure succeed
|
45
|
+
self._plugins[plugin.name] = plugin
|
46
|
+
print(f"Successfully registered plugin {plugin.name}")
|
48
47
|
return True
|
48
|
+
|
49
49
|
except Exception as e:
|
50
50
|
print(f"Error registering plugin {plugin.name}: {e}")
|
51
|
+
# Remove plugin from registry if it was added
|
52
|
+
self._plugins.pop(plugin.name, None)
|
51
53
|
return False
|
52
54
|
|
53
55
|
def load_plugins(self) -> List[str]:
|
@@ -59,30 +61,27 @@ class PluginManager(PluginManagerInterface):
|
|
59
61
|
loaded_plugins = []
|
60
62
|
|
61
63
|
# Discover plugins through entry points
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
print(f"Error loading plugin {entry_point.name}: {e}")
|
84
|
-
except Exception as e:
|
85
|
-
print(f"Error discovering plugins: {e}")
|
64
|
+
for entry_point in importlib.metadata.entry_points(group='solana_agent.plugins'):
|
65
|
+
# Skip if this entry point has already been loaded
|
66
|
+
entry_point_id = f"{entry_point.name}:{entry_point.value}"
|
67
|
+
if entry_point_id in PluginManager._loaded_entry_points:
|
68
|
+
print(
|
69
|
+
f"Skipping already loaded plugin: {entry_point.name}")
|
70
|
+
continue
|
71
|
+
|
72
|
+
try:
|
73
|
+
print(f"Found plugin entry point: {entry_point.name}")
|
74
|
+
PluginManager._loaded_entry_points.add(entry_point_id)
|
75
|
+
plugin_factory = entry_point.load()
|
76
|
+
plugin = plugin_factory()
|
77
|
+
|
78
|
+
# Register the plugin
|
79
|
+
if self.register_plugin(plugin):
|
80
|
+
# Use entry_point.name instead of plugin.name
|
81
|
+
loaded_plugins.append(entry_point.name)
|
82
|
+
|
83
|
+
except Exception as e:
|
84
|
+
print(f"Error loading plugin {entry_point.name}: {e}")
|
86
85
|
|
87
86
|
return loaded_plugins
|
88
87
|
|
solana_agent/plugins/registry.py
CHANGED
@@ -76,6 +76,23 @@ class ToolRegistry(ToolRegistryInterface):
|
|
76
76
|
return list(self._tools.keys())
|
77
77
|
|
78
78
|
def configure_all_tools(self, config: Dict[str, Any]) -> None:
|
79
|
-
"""Configure all registered tools with
|
80
|
-
|
81
|
-
|
79
|
+
"""Configure all registered tools with new configuration.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
config: Configuration dictionary to apply
|
83
|
+
"""
|
84
|
+
self._config.update(config)
|
85
|
+
configure_errors = []
|
86
|
+
|
87
|
+
for name, tool in self._tools.items():
|
88
|
+
try:
|
89
|
+
print(f"Configuring tool: {name}")
|
90
|
+
tool.configure(self._config)
|
91
|
+
except Exception as e:
|
92
|
+
print(f"Error configuring tool {name}: {e}")
|
93
|
+
configure_errors.append((name, str(e)))
|
94
|
+
|
95
|
+
if configure_errors:
|
96
|
+
print("The following tools failed to configure:")
|
97
|
+
for name, error in configure_errors:
|
98
|
+
print(f"- {name}: {error}")
|
@@ -34,6 +34,8 @@ class AutoTool(Tool):
|
|
34
34
|
|
35
35
|
def configure(self, config: Dict[str, Any]) -> None:
|
36
36
|
"""Configure the tool with settings from config."""
|
37
|
+
if config is None:
|
38
|
+
raise TypeError("Config cannot be None")
|
37
39
|
self._config = config
|
38
40
|
|
39
41
|
def get_schema(self) -> Dict[str, Any]:
|
@@ -25,10 +25,13 @@ class MemoryRepository(MemoryProvider):
|
|
25
25
|
self.mongo = mongo_adapter
|
26
26
|
self.collection = "conversations"
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
try:
|
29
|
+
# Ensure MongoDB collection and indexes
|
30
|
+
self.mongo.create_collection(self.collection)
|
31
|
+
self.mongo.create_index(self.collection, [("user_id", 1)])
|
32
|
+
self.mongo.create_index(self.collection, [("timestamp", 1)])
|
33
|
+
except Exception as e:
|
34
|
+
print(f"Error initializing MongoDB: {e}")
|
32
35
|
|
33
36
|
# Initialize Zep
|
34
37
|
if zep_api_key and not zep_base_url:
|
@@ -40,53 +43,78 @@ class MemoryRepository(MemoryProvider):
|
|
40
43
|
|
41
44
|
async def store(self, user_id: str, messages: List[Dict[str, Any]]) -> None:
|
42
45
|
"""Store messages in both Zep and MongoDB."""
|
43
|
-
|
44
|
-
|
46
|
+
if not user_id:
|
47
|
+
raise ValueError("User ID cannot be None or empty")
|
48
|
+
if not messages or not isinstance(messages, list):
|
49
|
+
raise ValueError("Messages must be a non-empty list")
|
50
|
+
if not all(isinstance(msg, dict) and "role" in msg and "content" in msg for msg in messages):
|
51
|
+
raise ValueError(
|
52
|
+
"All messages must be dictionaries with 'role' and 'content' keys")
|
53
|
+
for msg in messages:
|
54
|
+
if msg["role"] not in ["user", "assistant"]:
|
55
|
+
raise ValueError(
|
56
|
+
f"Invalid role '{msg['role']}' in message. Only 'user' and 'assistant' roles are accepted.")
|
57
|
+
|
58
|
+
# Store in MongoDB
|
59
|
+
if self.mongo and len(messages) >= 2:
|
45
60
|
try:
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
61
|
+
# Get last user and assistant messages
|
62
|
+
user_msg = None
|
63
|
+
assistant_msg = None
|
64
|
+
for msg in reversed(messages):
|
65
|
+
if msg.get("role") == "user" and not user_msg:
|
66
|
+
user_msg = msg.get("content")
|
67
|
+
elif msg.get("role") == "assistant" and not assistant_msg:
|
68
|
+
assistant_msg = msg.get("content")
|
69
|
+
if user_msg and assistant_msg:
|
70
|
+
break
|
71
|
+
|
72
|
+
if user_msg and assistant_msg:
|
73
|
+
# Store truncated messages
|
74
|
+
doc = {
|
75
|
+
"user_id": user_id,
|
76
|
+
"user_message": self._truncate(user_msg),
|
77
|
+
"assistant_message": self._truncate(assistant_msg),
|
78
|
+
"timestamp": datetime.now(timezone.utc)
|
79
|
+
}
|
80
|
+
self.mongo.insert_one(self.collection, doc)
|
59
81
|
except Exception as e:
|
60
82
|
print(f"MongoDB storage error: {e}")
|
61
83
|
|
62
|
-
# Store in Zep
|
84
|
+
# Store in Zep
|
63
85
|
if not self.zep:
|
64
86
|
return
|
65
87
|
|
66
88
|
try:
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
)
|
76
|
-
except Exception:
|
77
|
-
pass
|
89
|
+
await self.zep.user.add(user_id=user_id)
|
90
|
+
except Exception as e:
|
91
|
+
print(f"Zep user addition error: {e}")
|
92
|
+
|
93
|
+
try:
|
94
|
+
await self.zep.memory.add_session(session_id=user_id, user_id=user_id)
|
95
|
+
except Exception as e:
|
96
|
+
print(f"Zep session creation error: {e}")
|
78
97
|
|
79
|
-
|
80
|
-
|
98
|
+
# Convert messages to Zep format
|
99
|
+
zep_messages = []
|
100
|
+
for msg in messages:
|
101
|
+
if "role" in msg and "content" in msg:
|
102
|
+
zep_msg = Message(
|
81
103
|
role=msg["role"],
|
104
|
+
content=msg["content"],
|
82
105
|
role_type=msg["role"],
|
83
|
-
content=self._truncate(msg["content"])
|
84
106
|
)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
107
|
+
zep_messages.append(zep_msg)
|
108
|
+
|
109
|
+
# Add messages to Zep memory
|
110
|
+
if zep_messages:
|
111
|
+
try:
|
112
|
+
await self.zep.memory.add(
|
113
|
+
session_id=user_id,
|
114
|
+
messages=zep_messages
|
115
|
+
)
|
116
|
+
except Exception as e:
|
117
|
+
print(f"Zep memory addition error: {e}")
|
90
118
|
|
91
119
|
async def retrieve(self, user_id: str) -> str:
|
92
120
|
"""Retrieve memory context from Zep only."""
|
@@ -95,7 +123,8 @@ class MemoryRepository(MemoryProvider):
|
|
95
123
|
|
96
124
|
try:
|
97
125
|
memory = await self.zep.memory.get(session_id=user_id)
|
98
|
-
|
126
|
+
if memory is None or not hasattr(memory, 'context') or memory.context is None:
|
127
|
+
return ""
|
99
128
|
return memory.context
|
100
129
|
|
101
130
|
except Exception as e:
|
@@ -118,9 +147,13 @@ class MemoryRepository(MemoryProvider):
|
|
118
147
|
|
119
148
|
try:
|
120
149
|
await self.zep.memory.delete(session_id=user_id)
|
150
|
+
except Exception as e:
|
151
|
+
print(f"Zep memory deletion error: {e}")
|
152
|
+
|
153
|
+
try:
|
121
154
|
await self.zep.user.delete(user_id=user_id)
|
122
155
|
except Exception as e:
|
123
|
-
print(f"Zep deletion error: {e}")
|
156
|
+
print(f"Zep user deletion error: {e}")
|
124
157
|
|
125
158
|
def find(
|
126
159
|
self,
|
@@ -129,26 +162,38 @@ class MemoryRepository(MemoryProvider):
|
|
129
162
|
sort: Optional[List[Tuple]] = None,
|
130
163
|
limit: int = 0,
|
131
164
|
skip: int = 0
|
132
|
-
) -> List[Dict]:
|
133
|
-
"""Find documents
|
165
|
+
) -> List[Dict]: # pragma: no cover
|
166
|
+
"""Find documents in MongoDB."""
|
134
167
|
if not self.mongo:
|
135
168
|
return []
|
136
|
-
|
169
|
+
|
170
|
+
try:
|
171
|
+
return self.mongo.find(collection, query, sort=sort, limit=limit, skip=skip)
|
172
|
+
except Exception as e:
|
173
|
+
print(f"MongoDB find error: {e}")
|
174
|
+
return []
|
137
175
|
|
138
176
|
def count_documents(self, collection: str, query: Dict) -> int:
|
139
|
-
"""Count documents
|
177
|
+
"""Count documents in MongoDB."""
|
140
178
|
if not self.mongo:
|
141
179
|
return 0
|
142
180
|
return self.mongo.count_documents(collection, query)
|
143
181
|
|
144
182
|
def _truncate(self, text: str, limit: int = 2500) -> str:
|
145
183
|
"""Truncate text to be within limits."""
|
184
|
+
if text is None:
|
185
|
+
raise AttributeError("Cannot truncate None text")
|
186
|
+
|
187
|
+
if not text:
|
188
|
+
return ""
|
189
|
+
|
146
190
|
if len(text) <= limit:
|
147
191
|
return text
|
148
192
|
|
149
|
-
|
150
|
-
last_period =
|
151
|
-
if last_period >
|
152
|
-
return
|
193
|
+
# Try to truncate at last period before limit
|
194
|
+
last_period = text.rfind('.', 0, limit)
|
195
|
+
if last_period > 0:
|
196
|
+
return text[:last_period + 1]
|
153
197
|
|
154
|
-
|
198
|
+
# If no period found, truncate at limit and add ellipsis
|
199
|
+
return text[:limit] + "..."
|
solana_agent/services/agent.py
CHANGED
@@ -12,7 +12,7 @@ from typing import AsyncGenerator, Dict, List, Literal, Optional, Any, Union
|
|
12
12
|
|
13
13
|
from solana_agent.interfaces.services.agent import AgentService as AgentServiceInterface
|
14
14
|
from solana_agent.interfaces.providers.llm import LLMProvider
|
15
|
-
from solana_agent.
|
15
|
+
from solana_agent.plugins.manager import PluginManager
|
16
16
|
from solana_agent.plugins.registry import ToolRegistry
|
17
17
|
from solana_agent.domains.agent import AIAgent, BusinessMission
|
18
18
|
|
@@ -40,8 +40,10 @@ class AgentService(AgentServiceInterface):
|
|
40
40
|
self.tool_registry = ToolRegistry(config=self.config)
|
41
41
|
self.agents: List[AIAgent] = []
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
self.plugin_manager = PluginManager(
|
44
|
+
config=self.config,
|
45
|
+
tool_registry=self.tool_registry,
|
46
|
+
)
|
45
47
|
|
46
48
|
def register_ai_agent(
|
47
49
|
self, name: str, instructions: str, specialization: str,
|
solana_agent/services/query.py
CHANGED
@@ -8,6 +8,7 @@ clean separation of concerns.
|
|
8
8
|
from typing import Any, AsyncGenerator, Dict, Literal, Optional, Union
|
9
9
|
|
10
10
|
from solana_agent.interfaces.services.query import QueryService as QueryServiceInterface
|
11
|
+
from solana_agent.interfaces.services.routing import RoutingService as RoutingServiceInterface
|
11
12
|
from solana_agent.services.agent import AgentService
|
12
13
|
from solana_agent.services.routing import RoutingService
|
13
14
|
from solana_agent.interfaces.providers.memory import MemoryProvider
|
@@ -47,6 +48,7 @@ class QueryService(QueryServiceInterface):
|
|
47
48
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
48
49
|
] = "mp4",
|
49
50
|
prompt: Optional[str] = None,
|
51
|
+
router: Optional[RoutingServiceInterface] = None,
|
50
52
|
) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
|
51
53
|
"""Process the user request with appropriate agent.
|
52
54
|
|
@@ -59,6 +61,7 @@ class QueryService(QueryServiceInterface):
|
|
59
61
|
audio_output_format: Audio output format
|
60
62
|
audio_input_format: Audio input format
|
61
63
|
prompt: Optional prompt for the agent
|
64
|
+
router: Optional routing service for processing
|
62
65
|
|
63
66
|
Yields:
|
64
67
|
Response chunks (text strings or audio bytes)
|
@@ -96,7 +99,11 @@ class QueryService(QueryServiceInterface):
|
|
96
99
|
memory_context = await self.memory_provider.retrieve(user_id)
|
97
100
|
|
98
101
|
# Route query to appropriate agent
|
99
|
-
|
102
|
+
if router:
|
103
|
+
agent_name = await router.route_query(user_text)
|
104
|
+
else:
|
105
|
+
agent_name = await self.routing_service.route_query(user_text)
|
106
|
+
|
100
107
|
print(f"Routed to agent: {agent_name}")
|
101
108
|
|
102
109
|
# Generate response
|
solana_agent/services/routing.py
CHANGED
@@ -1,23 +1,25 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: solana-agent
|
3
|
-
Version:
|
3
|
+
Version: 20.1.4
|
4
4
|
Summary: Agentic IQ
|
5
5
|
License: MIT
|
6
6
|
Keywords: ai,openai,ai agents,agi
|
7
7
|
Author: Bevan Hunt
|
8
8
|
Author-email: bevan@bevanhunt.com
|
9
9
|
Requires-Python: >=3.12,<4.0
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
11
|
+
Classifier: Intended Audience :: Developers
|
10
12
|
Classifier: License :: OSI Approved :: MIT License
|
11
13
|
Classifier: Programming Language :: Python :: 3
|
12
14
|
Classifier: Programming Language :: Python :: 3.12
|
13
15
|
Classifier: Programming Language :: Python :: 3.13
|
14
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
15
16
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
16
|
-
Requires-Dist: openai (>=1.
|
17
|
-
Requires-Dist: pydantic (>=2.11.
|
17
|
+
Requires-Dist: openai (>=1.68.2,<2.0.0)
|
18
|
+
Requires-Dist: pydantic (>=2.11.1,<3.0.0)
|
18
19
|
Requires-Dist: pymongo (>=4.11.3,<5.0.0)
|
19
20
|
Requires-Dist: zep-cloud (>=2.8.0,<3.0.0)
|
20
21
|
Requires-Dist: zep-python (>=2.0.2,<3.0.0)
|
22
|
+
Project-URL: Documentation, https://docs.solana-agent.com
|
21
23
|
Project-URL: Repository, https://github.com/truemagic-coder/solana-agent
|
22
24
|
Description-Content-Type: text/markdown
|
23
25
|
|
@@ -46,6 +48,7 @@ Build your AI business in three lines of code!
|
|
46
48
|
* Extensible Tooling
|
47
49
|
* Simple Business Definition
|
48
50
|
* Tested & Secure
|
51
|
+
* Support for MCP Servers
|
49
52
|
* Built in Python
|
50
53
|
* Deployed by [CometHeart](https://cometheart.com) & [WalletBubbles](https://walletbubbles.com)
|
51
54
|
|
@@ -54,11 +57,12 @@ Build your AI business in three lines of code!
|
|
54
57
|
* Seamless text and audio streaming with real-time multi-modal processing
|
55
58
|
* Persistent memory that preserves context across all agent interactions
|
56
59
|
* Streamlined message history for all agent interactions
|
57
|
-
* Intelligent query routing to agents with optimal domain expertise
|
60
|
+
* Intelligent query routing to agents with optimal domain expertise or your own custom routing
|
58
61
|
* Unified value system ensuring brand-aligned agent responses
|
59
62
|
* Powerful tool integration using standard Python packages and/or inline classes
|
60
63
|
* Assigned tools are utilized by agents automatically and effectively
|
61
64
|
* Simple business definition using JSON
|
65
|
+
* Ability to access any MCP server via a URL
|
62
66
|
|
63
67
|
## Stack
|
64
68
|
|
@@ -73,13 +77,13 @@ You can install Solana Agent using pip:
|
|
73
77
|
|
74
78
|
`pip install solana-agent`
|
75
79
|
|
76
|
-
##
|
80
|
+
## Usage
|
77
81
|
|
78
|
-
|
79
|
-
from solana_agent import SolanaAgent
|
82
|
+
### Business Alignment Config - Optional
|
80
83
|
|
84
|
+
```python
|
81
85
|
config = {
|
82
|
-
"business": {
|
86
|
+
"business": {
|
83
87
|
"mission": "To provide users with a one-stop shop for their queries.",
|
84
88
|
"values": {
|
85
89
|
"Friendliness": "Users must be treated fairly, openly, and with friendliness.",
|
@@ -90,14 +94,37 @@ config = {
|
|
90
94
|
],
|
91
95
|
"voice": "The voice of the brand is that of a research business."
|
92
96
|
},
|
93
|
-
|
97
|
+
}
|
98
|
+
```
|
99
|
+
|
100
|
+
### Conversational History Config - Optional
|
101
|
+
|
102
|
+
```python
|
103
|
+
config = {
|
104
|
+
"mongo": {
|
94
105
|
"connection_string": "mongodb://localhost:27017",
|
95
106
|
"database": "solana_agent"
|
96
107
|
},
|
97
|
-
|
108
|
+
}
|
109
|
+
```
|
110
|
+
|
111
|
+
### Conversational Memory Config - Optional
|
112
|
+
|
113
|
+
```python
|
114
|
+
config = {
|
115
|
+
"zep": {
|
98
116
|
"api_key": "your-zep-api-key",
|
99
117
|
"base_url": "your-zep-base-url", # not applicable if using Zep Cloud
|
100
118
|
},
|
119
|
+
}
|
120
|
+
```
|
121
|
+
|
122
|
+
### Text/Text Streaming
|
123
|
+
|
124
|
+
```python
|
125
|
+
from solana_agent import SolanaAgent
|
126
|
+
|
127
|
+
config = {
|
101
128
|
"openai": {
|
102
129
|
"api_key": "your-openai-api-key",
|
103
130
|
},
|
@@ -121,40 +148,110 @@ async for response in solana_agent.process("user123", "What are the latest AI de
|
|
121
148
|
print(response, end="")
|
122
149
|
```
|
123
150
|
|
124
|
-
|
151
|
+
### Audio/Audio Streaming
|
125
152
|
|
126
|
-
|
153
|
+
```python
|
154
|
+
from solana_agent import SolanaAgent
|
127
155
|
|
128
|
-
|
156
|
+
config = {
|
157
|
+
"openai": {
|
158
|
+
"api_key": "your-openai-api-key",
|
159
|
+
},
|
160
|
+
"agents": [
|
161
|
+
{
|
162
|
+
"name": "research_specialist",
|
163
|
+
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
164
|
+
"specialization": "Research and knowledge synthesis",
|
165
|
+
},
|
166
|
+
{
|
167
|
+
"name": "customer_support",
|
168
|
+
"instructions": "You provide friendly, helpful customer support responses.",
|
169
|
+
"specialization": "Customer inquiries",
|
170
|
+
}
|
171
|
+
],
|
172
|
+
}
|
173
|
+
|
174
|
+
solana_agent = SolanaAgent(config=config)
|
175
|
+
|
176
|
+
audio_content = audio_file.read()
|
177
|
+
|
178
|
+
async for response in solana_agent.process("user123", audio_content, output_format="audio", audio_voice="nova", audio_input_format="webm", audio_output_format="aac"):
|
179
|
+
print(response, end="")
|
180
|
+
```
|
181
|
+
|
182
|
+
### Text/Audio Streaming
|
129
183
|
|
130
184
|
```python
|
131
185
|
from solana_agent import SolanaAgent
|
132
186
|
|
133
187
|
config = {
|
134
|
-
"
|
135
|
-
"mission": "To provide users with a one-stop shop for their queries.",
|
136
|
-
"values": {
|
137
|
-
"Friendliness": "Users must be treated fairly, openly, and with friendliness.",
|
138
|
-
"Ethical": "Agents must use a strong ethical framework in their interactions with users.",
|
139
|
-
},
|
140
|
-
"goals": [
|
141
|
-
"Empower users with great answers to their queries.",
|
142
|
-
],
|
143
|
-
"voice": "The voice of the brand is that of a research business."
|
144
|
-
},
|
145
|
-
"openai": { # optional
|
188
|
+
"openai": {
|
146
189
|
"api_key": "your-openai-api-key",
|
147
190
|
},
|
148
|
-
"
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
191
|
+
"agents": [
|
192
|
+
{
|
193
|
+
"name": "research_specialist",
|
194
|
+
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
195
|
+
"specialization": "Research and knowledge synthesis",
|
196
|
+
},
|
197
|
+
{
|
198
|
+
"name": "customer_support",
|
199
|
+
"instructions": "You provide friendly, helpful customer support responses.",
|
200
|
+
"specialization": "Customer inquiries",
|
201
|
+
}
|
202
|
+
],
|
203
|
+
}
|
204
|
+
|
205
|
+
solana_agent = SolanaAgent(config=config)
|
206
|
+
|
207
|
+
async for response in solana_agent.process("user123", "What is the latest news on Elon Musk?", output_format="audio", audio_voice="nova", audio_output_format="aac"):
|
208
|
+
print(response, end="")
|
209
|
+
```
|
210
|
+
|
211
|
+
### Audio/Text Streaming
|
212
|
+
|
213
|
+
```python
|
214
|
+
from solana_agent import SolanaAgent
|
215
|
+
|
216
|
+
config = {
|
217
|
+
"openai": {
|
218
|
+
"api_key": "your-openai-api-key",
|
154
219
|
},
|
155
|
-
"
|
156
|
-
|
157
|
-
|
220
|
+
"agents": [
|
221
|
+
{
|
222
|
+
"name": "research_specialist",
|
223
|
+
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
224
|
+
"specialization": "Research and knowledge synthesis",
|
225
|
+
},
|
226
|
+
{
|
227
|
+
"name": "customer_support",
|
228
|
+
"instructions": "You provide friendly, helpful customer support responses.",
|
229
|
+
"specialization": "Customer inquiries",
|
230
|
+
}
|
231
|
+
],
|
232
|
+
}
|
233
|
+
|
234
|
+
solana_agent = SolanaAgent(config=config)
|
235
|
+
|
236
|
+
audio_content = audio_file.read()
|
237
|
+
|
238
|
+
async for response in solana_agent.process("user123", audio_content, audio_input_format="aac"):
|
239
|
+
print(response, end="")
|
240
|
+
```
|
241
|
+
|
242
|
+
### Plugins
|
243
|
+
|
244
|
+
Plugins like Solana Agent Kit (sakit) integrate automatically with Solana Agent.
|
245
|
+
|
246
|
+
`pip install sakit`
|
247
|
+
|
248
|
+
#### Internet Search
|
249
|
+
```python
|
250
|
+
from solana_agent import SolanaAgent
|
251
|
+
|
252
|
+
config = {
|
253
|
+
"openai": {
|
254
|
+
"api_key": "your-openai-api-key",
|
158
255
|
},
|
159
256
|
"tools": {
|
160
257
|
"search_internet": {
|
@@ -184,9 +281,47 @@ async for response in solana_agent.process("user123", "What are the latest AI de
|
|
184
281
|
print(response, end="")
|
185
282
|
```
|
186
283
|
|
284
|
+
#### MCP
|
285
|
+
```python
|
286
|
+
from solana_agent import SolanaAgent
|
287
|
+
|
288
|
+
config = {
|
289
|
+
"openai": {
|
290
|
+
"api_key": "your-openai-api-key",
|
291
|
+
},
|
292
|
+
"tools": {
|
293
|
+
"mcp": {
|
294
|
+
"server_urls": [
|
295
|
+
"http://mcp-server1.com/mcp",
|
296
|
+
"http://mcp-server2.com/mcp",
|
297
|
+
"http://mcp-server3.com/mcp"
|
298
|
+
]
|
299
|
+
}
|
300
|
+
},
|
301
|
+
"agents": [
|
302
|
+
{
|
303
|
+
"name": "research_specialist",
|
304
|
+
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
305
|
+
"specialization": "Research and knowledge synthesis",
|
306
|
+
"tools": ["mcp"],
|
307
|
+
},
|
308
|
+
{
|
309
|
+
"name": "customer_support",
|
310
|
+
"instructions": "You provide friendly, helpful customer support responses.",
|
311
|
+
"specialization": "Customer inquiries",
|
312
|
+
}
|
313
|
+
],
|
314
|
+
}
|
315
|
+
|
316
|
+
solana_agent = SolanaAgent(config=config)
|
317
|
+
|
318
|
+
async for response in solana_agent.process("user123", "What are the latest AI developments?"):
|
319
|
+
print(response, end="")
|
320
|
+
```
|
321
|
+
|
187
322
|
To create a plugin like Solana Agent Kit - read the [code](https://github.com/truemagic-coder/solana-agent-kit)
|
188
323
|
|
189
|
-
|
324
|
+
### Custom Inline Tools
|
190
325
|
|
191
326
|
```python
|
192
327
|
from solana_agent import SolanaAgent
|
@@ -239,31 +374,9 @@ class TestTool(Tool):
|
|
239
374
|
}
|
240
375
|
|
241
376
|
config = {
|
242
|
-
"
|
243
|
-
"mission": "To provide users with a one-stop shop for their queries.",
|
244
|
-
"values": {
|
245
|
-
"Friendliness": "Users must be treated fairly, openly, and with friendliness.",
|
246
|
-
"Ethical": "Agents must use a strong ethical framework in their interactions with users.",
|
247
|
-
},
|
248
|
-
"goals": [
|
249
|
-
"Empower users with great answers to their queries.",
|
250
|
-
],
|
251
|
-
"voice": "The voice of the brand is that of a research business."
|
252
|
-
},
|
253
|
-
"openai": { # optional
|
377
|
+
"openai": {
|
254
378
|
"api_key": "your-openai-api-key",
|
255
379
|
},
|
256
|
-
"ollama": { # optional
|
257
|
-
"url": "your-ollama-url",
|
258
|
-
},
|
259
|
-
"mongo": { # optional
|
260
|
-
"connection_string": "mongodb://localhost:27017",
|
261
|
-
"database": "solana_agent"
|
262
|
-
},
|
263
|
-
"zep": { # optional
|
264
|
-
"api_key": "your-zep-api-key",
|
265
|
-
"base_url": "your-zep-base-url", # not applicable if using Zep Cloud
|
266
|
-
},
|
267
380
|
"agents": [
|
268
381
|
{
|
269
382
|
"name": "research_specialist",
|
@@ -288,12 +401,81 @@ async for response in solana_agent.process("user123", "What are the latest AI de
|
|
288
401
|
print(response, end="")
|
289
402
|
```
|
290
403
|
|
404
|
+
### Custom Prompt Injection at Runtime
|
405
|
+
|
406
|
+
```python
|
407
|
+
from solana_agent import SolanaAgent
|
408
|
+
|
409
|
+
config = {
|
410
|
+
"openai": {
|
411
|
+
"api_key": "your-openai-api-key",
|
412
|
+
},
|
413
|
+
"agents": [
|
414
|
+
{
|
415
|
+
"name": "research_specialist",
|
416
|
+
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
417
|
+
"specialization": "Research and knowledge synthesis",
|
418
|
+
},
|
419
|
+
{
|
420
|
+
"name": "customer_support",
|
421
|
+
"instructions": "You provide friendly, helpful customer support responses.",
|
422
|
+
"specialization": "Customer inquiries",
|
423
|
+
}
|
424
|
+
],
|
425
|
+
}
|
426
|
+
|
427
|
+
solana_agent = SolanaAgent(config=config)
|
428
|
+
|
429
|
+
async for response in solana_agent.process("user123", "What are the latest AI developments?", "Always end your sentences with eh?"):
|
430
|
+
print(response, end="")
|
431
|
+
```
|
432
|
+
|
433
|
+
### Custom Routing
|
434
|
+
|
435
|
+
```python
|
436
|
+
from solana_agent import SolanaAgent
|
437
|
+
from solana_agent.interfaces.services.routing import RoutingService as RoutingServiceInterface
|
438
|
+
|
439
|
+
config = {
|
440
|
+
"openai": {
|
441
|
+
"api_key": "your-openai-api-key",
|
442
|
+
},
|
443
|
+
"agents": [
|
444
|
+
{
|
445
|
+
"name": "research_specialist",
|
446
|
+
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
447
|
+
"specialization": "Research and knowledge synthesis",
|
448
|
+
},
|
449
|
+
{
|
450
|
+
"name": "customer_support",
|
451
|
+
"instructions": "You provide friendly, helpful customer support responses.",
|
452
|
+
"specialization": "Customer inquiries",
|
453
|
+
}
|
454
|
+
],
|
455
|
+
}
|
456
|
+
|
457
|
+
class Router(RoutingServiceInterface)
|
458
|
+
def __init__(self):
|
459
|
+
# your router initialization - delete the following pass
|
460
|
+
pass
|
461
|
+
|
462
|
+
async def route_query(self, query: str) -> str:
|
463
|
+
# a simple example to route always to customer_support agent
|
464
|
+
return "customer_support"
|
465
|
+
|
466
|
+
router = Router()
|
467
|
+
|
468
|
+
solana_agent = SolanaAgent(config=config)
|
469
|
+
|
470
|
+
async for response in solana_agent.process("user123", "What are the latest AI developments?", router=router):
|
471
|
+
print(response, end="")
|
472
|
+
```
|
473
|
+
|
291
474
|
## Notes
|
292
475
|
* Solana Agent agents can only call one tool per response.
|
293
476
|
* Solana Agent agents choose the best tool for the job.
|
294
477
|
* Solana Agent tools do not use OpenAI function calling.
|
295
478
|
* Solana Agent tools are async functions.
|
296
|
-
* Solana Agent will use OpenAI for audio and Ollama and for text if both config vars are set
|
297
479
|
|
298
480
|
## Local Setup
|
299
481
|
|
@@ -3,12 +3,12 @@ solana_agent/adapters/__init__.py,sha256=tiEEuuy0NF3ngc_tGEcRTt71zVI58v3dYY9RvMr
|
|
3
3
|
solana_agent/adapters/llm_adapter.py,sha256=3_7whVsPSJdlzBVUBlV7RBRCCo2dMXNmlACIrCoQxQ4,5426
|
4
4
|
solana_agent/adapters/mongodb_adapter.py,sha256=qqEFbY_v1XGyFXBmwd5HSXSSHnA9wWo-Hm1vGEyIG0k,2718
|
5
5
|
solana_agent/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
solana_agent/client/solana_agent.py,sha256=
|
6
|
+
solana_agent/client/solana_agent.py,sha256=4xXPNNcULMEIx-V-_zu2IH0tlPjQU2ZzkIqZFuxAJHY,5206
|
7
7
|
solana_agent/domains/__init__.py,sha256=HiC94wVPRy-QDJSSRywCRrhrFfTBeHjfi5z-QfZv46U,168
|
8
8
|
solana_agent/domains/agent.py,sha256=WTo-pEc66V6D_35cpDE-kTsw1SJM-dtylPZ7em5em7Q,2659
|
9
9
|
solana_agent/domains/routing.py,sha256=UDlgTjUoC9xIBVYu_dnf9-KG_bBgdEXAv_UtDOrYo0w,650
|
10
10
|
solana_agent/factories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
solana_agent/factories/agent_factory.py,sha256=
|
11
|
+
solana_agent/factories/agent_factory.py,sha256=c5TgZpDZ6m_q8F-Bv36Rl4dXq5yWNvAN2q9Nq_PtqqM,5547
|
12
12
|
solana_agent/interfaces/__init__.py,sha256=IQs1WIM1FeKP1-kY2FEfyhol_dB-I-VAe2rD6jrVF6k,355
|
13
13
|
solana_agent/interfaces/client/client.py,sha256=c9n5uF7UzwqCTU-jFz-33eq9TRyora_A5UbO4-EYlJU,1503
|
14
14
|
solana_agent/interfaces/plugins/plugins.py,sha256=T8HPBsekmzVwfU_Rizp-vtzAeYkMlKMYD7U9d0Wjq9c,3338
|
@@ -17,19 +17,19 @@ solana_agent/interfaces/providers/llm.py,sha256=f58kDrvESBfIr2XoZJ-VVa8vL56qyuhk
|
|
17
17
|
solana_agent/interfaces/providers/memory.py,sha256=oNOH8WZXVW8assDigIWZAWiwkxbpDiKupxA2RB6tQvQ,1010
|
18
18
|
solana_agent/interfaces/services/agent.py,sha256=34luGrUF5FNXLhF6JXwbfOSuo_SbMOmLMywG310sMDw,2082
|
19
19
|
solana_agent/interfaces/services/query.py,sha256=m8Uc0uXT3apSOhX3N1QjLMPk1KdJhj7HDrJjWUpDPBc,1309
|
20
|
-
solana_agent/interfaces/services/routing.py,sha256=
|
20
|
+
solana_agent/interfaces/services/routing.py,sha256=UzJC-z-Q9puTWPFGEo2_CAhIxuxP5IRnze7S66NSrsI,397
|
21
21
|
solana_agent/plugins/__init__.py,sha256=coZdgJKq1ExOaj6qB810i3rEhbjdVlrkN76ozt_Ojgo,193
|
22
|
-
solana_agent/plugins/manager.py,sha256=
|
23
|
-
solana_agent/plugins/registry.py,sha256=
|
22
|
+
solana_agent/plugins/manager.py,sha256=Il49hXeqvu0b02pURNNp7mY8kp9_sqpi_vJIWBW5Hc0,5044
|
23
|
+
solana_agent/plugins/registry.py,sha256=5S0DlUQKogsg1zLiRUIGMHEmGYHtOovU-S-5W1Mwo1A,3639
|
24
24
|
solana_agent/plugins/tools/__init__.py,sha256=c0z7ij42gs94_VJrcn4Y8gUlTxMhsFNY6ahIsNswdLk,231
|
25
|
-
solana_agent/plugins/tools/auto_tool.py,sha256=
|
25
|
+
solana_agent/plugins/tools/auto_tool.py,sha256=DgES_cZ6xKSf_HJpFINpvJxrjVlk5oeqa7pZRBsR9SM,1575
|
26
26
|
solana_agent/repositories/__init__.py,sha256=fP83w83CGzXLnSdq-C5wbw9EhWTYtqE2lQTgp46-X_4,163
|
27
|
-
solana_agent/repositories/memory.py,sha256=
|
27
|
+
solana_agent/repositories/memory.py,sha256=eecl1P0fr_xFSWFKIJg99q90oCS9--ihPrMLH3G2AzM,7136
|
28
28
|
solana_agent/services/__init__.py,sha256=ab_NXJmwYUCmCrCzuTlZ47bJZINW0Y0F5jfQ9OovidU,163
|
29
|
-
solana_agent/services/agent.py,sha256=
|
30
|
-
solana_agent/services/query.py,sha256=
|
31
|
-
solana_agent/services/routing.py,sha256=
|
32
|
-
solana_agent-
|
33
|
-
solana_agent-
|
34
|
-
solana_agent-
|
35
|
-
solana_agent-
|
29
|
+
solana_agent/services/agent.py,sha256=MdSPIC81JNuP2hfzXNGWOnRfe7OxwYHgDVZAphVCCo8,16450
|
30
|
+
solana_agent/services/query.py,sha256=QduAeiltFTwNDlAbC_emu544U4XGNioj-OauRGt9HSY,11070
|
31
|
+
solana_agent/services/routing.py,sha256=PMCSG5m3uLMaHMj3dxNvNfcFZaeaDi7kMr7AEBCzwDE,6499
|
32
|
+
solana_agent-20.1.4.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
|
33
|
+
solana_agent-20.1.4.dist-info/METADATA,sha256=fnqm3cRwxnmHdvW2-2H1Xlg5n-h10Tzyh3rKE3G7VnI,15083
|
34
|
+
solana_agent-20.1.4.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
35
|
+
solana_agent-20.1.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|