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.
@@ -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: Optional instructions for audio synthesis
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
- try:
129
- print(f"Attempting to register tool: {tool.name}")
130
- success = self.query_service.agent_service.tool_registry.register_tool(
131
- tool)
132
- if success:
133
- print(f"Tool {tool.name} registered successfully")
134
- # Get all agents and assign the tool to them
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
- loaded_plugins = agent_service.plugin_manager.load_plugins()
113
- print(f"Loaded {loaded_plugins} plugins")
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
- try:
129
- agent_service.assign_tool_for_agent(
130
- agent_config["name"], tool_name
131
- )
132
- print(
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
- try:
143
- agent_service.assign_tool_for_agent(
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) -> Tuple[str, Any]:
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:
@@ -35,19 +35,21 @@ class PluginManager(PluginManagerInterface):
35
35
  True if registration succeeded, False otherwise
36
36
  """
37
37
  try:
38
- # Store plugin by name
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
- # *** ADD THIS LINE: Configure the plugin with our config ***
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
- try:
63
- for entry_point in importlib.metadata.entry_points(group='solana_agent.plugins'):
64
- # Skip if this entry point has already been loaded
65
- entry_point_id = f"{entry_point.name}:{entry_point.value}"
66
- if entry_point_id in PluginManager._loaded_entry_points:
67
- print(
68
- f"Skipping already loaded plugin: {entry_point.name}")
69
- continue
70
-
71
- try:
72
- print(f"Found plugin entry point: {entry_point.name}")
73
- PluginManager._loaded_entry_points.add(entry_point_id)
74
- plugin_factory = entry_point.load()
75
- plugin = plugin_factory()
76
-
77
- # Register the plugin
78
- if self.register_plugin(plugin):
79
- # Use entry_point.name instead of plugin.name
80
- loaded_plugins.append(entry_point.name)
81
-
82
- except Exception as e:
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
 
@@ -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 the same config."""
80
- for tool in self._tools.values():
81
- tool.configure(config)
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
- # Ensure MongoDB collection and indexes
29
- self.mongo.create_collection(self.collection)
30
- self.mongo.create_index(self.collection, [("user_id", 1)])
31
- self.mongo.create_index(self.collection, [("timestamp", 1)])
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
- # Store in MongoDB as single document
44
- if self.mongo:
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
- # Extract user and assistant messages
47
- user_message = next(msg["content"]
48
- for msg in messages if msg["role"] == "user")
49
- assistant_message = next(
50
- msg["content"] for msg in messages if msg["role"] == "assistant")
51
-
52
- doc = {
53
- "user_id": user_id,
54
- "user_message": user_message,
55
- "assistant_message": assistant_message,
56
- "timestamp": datetime.now(timezone.utc)
57
- }
58
- self.mongo.insert_one(self.collection, doc)
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 with role-based format
84
+ # Store in Zep
63
85
  if not self.zep:
64
86
  return
65
87
 
66
88
  try:
67
- try:
68
- await self.zep.user.add(user_id=user_id)
69
- except Exception:
70
- pass
71
- try:
72
- await self.zep.memory.add_session(
73
- session_id=user_id,
74
- user_id=user_id,
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
- zep_messages = [
80
- Message(
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
- for msg in messages
86
- ]
87
- await self.zep.memory.add(session_id=user_id, messages=zep_messages)
88
- except Exception as e:
89
- print(f"Zep storage error: {e}")
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 matching query."""
165
+ ) -> List[Dict]: # pragma: no cover
166
+ """Find documents in MongoDB."""
134
167
  if not self.mongo:
135
168
  return []
136
- return self.mongo.find(collection, query, sort=sort, limit=limit, skip=skip)
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 matching query."""
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
- truncated = text[:limit]
150
- last_period = truncated.rfind(".")
151
- if last_period > limit * 0.8:
152
- return truncated[:last_period + 1]
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
- return truncated + "..."
198
+ # If no period found, truncate at limit and add ellipsis
199
+ return text[:limit] + "..."
@@ -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.interfaces.plugins.plugins import ToolRegistry as ToolRegistryInterface
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
- # Will be set by factory if plugin system is enabled
44
- self.plugin_manager = None
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,
@@ -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
- agent_name = await self.routing_service.route_query(user_text)
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
@@ -95,7 +95,7 @@ class RoutingService(RoutingServiceInterface):
95
95
  "confidence": 0.0
96
96
  }
97
97
 
98
- async def route_query(self, query: str) -> str:
98
+ async def route_query(self, query: str) -> str: # pragma: no cover
99
99
  """Route a query to the appropriate agent.
100
100
 
101
101
  Args:
@@ -1,23 +1,25 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solana-agent
3
- Version: 19.1.0
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.69.0,<2.0.0)
17
- Requires-Dist: pydantic (>=2.11.0,<3.0.0)
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
- ## Basic Usage
80
+ ## Usage
77
81
 
78
- ```python
79
- from solana_agent import SolanaAgent
82
+ ### Business Alignment Config - Optional
80
83
 
84
+ ```python
81
85
  config = {
82
- "business": { # optional
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
- "mongo": { # optional
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
- "zep": { # optional
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
- ## Plugin Usage
151
+ ### Audio/Audio Streaming
125
152
 
126
- Plugins like Solana Agent Kit (sakit) integrate automatically with Solana Agent.
153
+ ```python
154
+ from solana_agent import SolanaAgent
127
155
 
128
- `pip install sakit`
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
- "business": { # optional
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
- "ollama": { # optional
149
- "url": "your-ollama-url",
150
- },
151
- "mongo": { # optional
152
- "connection_string": "mongodb://localhost:27017",
153
- "database": "solana_agent"
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
- "zep": { # optional
156
- "api_key": "your-zep-api-key",
157
- "base_url": "your-zep-base-url", # not applicable if using Zep Cloud
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
- ## Custom Inline Tool Usage
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
- "business": { # optional
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=sN8imsCMm2SqsF4c4moumno1V6OPPAABv1IJw9P7QbU,5476
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=v_Q0bKPoZOQ8e6pS4gcYeCpE-jSbktOhVF5oaAXn_-I,5779
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=gohkt5f9uYDLpu4iDVDk9yj8js9P56R6QHSIDNylgwA,438
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=sdHq6Cqn4juvgS6_3Iu_yjLVAcTRoIkPCqw8fWPkxgw,5096
23
- solana_agent/plugins/registry.py,sha256=ZUeczZ9bkdm9G6NBMDpY7sglOW2IrJLiLhboMNAHors,3072
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=eDq2P-_D2PM7Dafpn55b1QU5LkM6BLdJGiWB9Wz8d_M,1495
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=xvgFscvnIGicIB9r1RwavhXSXGt2KRrloIheG3OWs-I,5221
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=o8RJg1pBREfTK5gCLS2A0Lprg36gvn0Hzh6_TS1Hx6Y,16448
30
- solana_agent/services/query.py,sha256=4MEGR_Q2THcCCjln-IcSAw7GGNqJ1q2cyEVRx69OQ7I,10746
31
- solana_agent/services/routing.py,sha256=IPvBicgTYXqQ8iIRaatCsBGQVsOBGdAkq2i6U8hZlOY,6479
32
- solana_agent-19.1.0.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
33
- solana_agent-19.1.0.dist-info/METADATA,sha256=DD7fvgmWqOjDOTYePcppCLZP-gs0kOu-S9mUyOFxV7w,10859
34
- solana_agent-19.1.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
35
- solana_agent-19.1.0.dist-info/RECORD,,
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,,