solana-agent 19.1.0__tar.gz → 20.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.
Files changed (36) hide show
  1. {solana_agent-19.1.0 → solana_agent-20.0.0}/PKG-INFO +6 -4
  2. {solana_agent-19.1.0 → solana_agent-20.0.0}/pyproject.toml +11 -5
  3. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/client/solana_agent.py +9 -20
  4. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/factories/agent_factory.py +13 -16
  5. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/plugins/manager.py +29 -30
  6. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/plugins/registry.py +20 -3
  7. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/plugins/tools/auto_tool.py +2 -0
  8. solana_agent-20.0.0/solana_agent/repositories/memory.py +199 -0
  9. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/services/agent.py +5 -2
  10. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/services/routing.py +1 -1
  11. solana_agent-19.1.0/solana_agent/repositories/memory.py +0 -154
  12. {solana_agent-19.1.0 → solana_agent-20.0.0}/LICENSE +0 -0
  13. {solana_agent-19.1.0 → solana_agent-20.0.0}/README.md +0 -0
  14. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/__init__.py +0 -0
  15. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/adapters/__init__.py +0 -0
  16. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/adapters/llm_adapter.py +0 -0
  17. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/adapters/mongodb_adapter.py +0 -0
  18. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/client/__init__.py +0 -0
  19. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/domains/__init__.py +0 -0
  20. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/domains/agent.py +0 -0
  21. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/domains/routing.py +0 -0
  22. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/factories/__init__.py +0 -0
  23. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/__init__.py +0 -0
  24. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/client/client.py +0 -0
  25. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/plugins/plugins.py +0 -0
  26. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/providers/data_storage.py +0 -0
  27. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/providers/llm.py +0 -0
  28. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/providers/memory.py +0 -0
  29. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/services/agent.py +0 -0
  30. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/services/query.py +0 -0
  31. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/interfaces/services/routing.py +0 -0
  32. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/plugins/__init__.py +0 -0
  33. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/plugins/tools/__init__.py +0 -0
  34. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/repositories/__init__.py +0 -0
  35. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/services/__init__.py +0 -0
  36. {solana_agent-19.1.0 → solana_agent-20.0.0}/solana_agent/services/query.py +0 -0
@@ -1,23 +1,25 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solana-agent
3
- Version: 19.1.0
3
+ Version: 20.0.0
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
 
@@ -1,14 +1,18 @@
1
1
  [tool.poetry]
2
2
  name = "solana-agent"
3
- version = "19.1.0"
3
+ version = "20.0.0"
4
4
  description = "Agentic IQ"
5
5
  authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
6
6
  license = "MIT"
7
7
  readme = "README.md"
8
8
  repository = "https://github.com/truemagic-coder/solana-agent"
9
+ documentation = "https://docs.solana-agent.com"
9
10
  keywords = ["ai", "openai", "ai agents", "agi"]
10
11
  classifiers = [
11
- "Programming Language :: Python :: 3 :: Only",
12
+ "Development Status :: 5 - Production/Stable",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
12
16
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
13
17
  ]
14
18
  packages = [{ include = "solana_agent" }]
@@ -18,21 +22,23 @@ python_paths = [".", "tests"]
18
22
 
19
23
  [tool.poetry.dependencies]
20
24
  python = ">=3.12,<4.0"
21
- openai = "^1.69.0"
22
- pydantic = "^2.11.0"
25
+ openai = "^1.68.2"
26
+ pydantic = "^2.11.1"
23
27
  pymongo = "^4.11.3"
24
28
  zep-cloud = "^2.8.0"
25
29
  zep-python = "^2.0.2"
26
30
 
27
- [tool.poetry.dev-dependencies]
31
+ [tool.poetry.group.dev.dependencies]
28
32
  pytest = "^8.3.5"
29
33
  pytest-cov = "^6.0.0"
30
34
  pytest-asyncio = "^0.26.0"
35
+ pytest-mock = "^3.14.0"
31
36
  pytest-github-actions-annotate-failures = "^0.3.0"
32
37
  sphinx = "^8.2.3"
33
38
  sphinx-rtd-theme = "^3.0.2"
34
39
  myst-parser = "^4.0.1"
35
40
  sphinx-autobuild = "^2024.10.3"
41
+ mongomock = "^4.3.0"
36
42
 
37
43
  [build-system]
38
44
  requires = ["poetry-core>=1.0.0"]
@@ -97,7 +97,7 @@ class SolanaAgent(SolanaAgentInterface):
97
97
  page_num: int = 1,
98
98
  page_size: int = 20,
99
99
  sort_order: str = "desc" # "asc" for oldest-first, "desc" for newest-first
100
- ) -> Dict[str, Any]:
100
+ ) -> Dict[str, Any]: # pragma: no cover
101
101
  """
102
102
  Get paginated message history for a user.
103
103
 
@@ -124,22 +124,11 @@ class SolanaAgent(SolanaAgentInterface):
124
124
  Returns:
125
125
  True if successful, False
126
126
  """
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
127
+ success = self.query_service.agent_service.tool_registry.register_tool(
128
+ tool)
129
+ if success:
130
+ agents = self.query_service.agent_service.get_all_ai_agents()
131
+ for agent_name in agents:
132
+ self.query_service.agent_service.assign_tool_for_agent(
133
+ agent_name, tool.name)
134
+ 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(
@@ -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]:
@@ -0,0 +1,199 @@
1
+ from typing import List, Dict, Any, Optional, Tuple
2
+ from datetime import datetime, timezone
3
+ from zep_cloud.client import AsyncZep as AsyncZepCloud
4
+ from zep_python.client import AsyncZep
5
+ from zep_cloud.types import Message
6
+ from solana_agent.interfaces.providers.memory import MemoryProvider
7
+ from solana_agent.adapters.mongodb_adapter import MongoDBAdapter
8
+
9
+
10
+ class MemoryRepository(MemoryProvider):
11
+ """Combined Zep and MongoDB implementation of MemoryProvider."""
12
+
13
+ def __init__(
14
+ self,
15
+ mongo_adapter: Optional[MongoDBAdapter] = None,
16
+ zep_api_key: Optional[str] = None,
17
+ zep_base_url: Optional[str] = None
18
+ ):
19
+ """Initialize the combined memory provider."""
20
+ if not mongo_adapter:
21
+ self.mongo = None
22
+ self.collection = None
23
+ else:
24
+ # Initialize MongoDB
25
+ self.mongo = mongo_adapter
26
+ self.collection = "conversations"
27
+
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}")
35
+
36
+ # Initialize Zep
37
+ if zep_api_key and not zep_base_url:
38
+ self.zep = AsyncZepCloud(api_key=zep_api_key)
39
+ elif zep_api_key and zep_base_url:
40
+ self.zep = AsyncZep(api_key=zep_api_key, base_url=zep_base_url)
41
+ else:
42
+ self.zep = None
43
+
44
+ async def store(self, user_id: str, messages: List[Dict[str, Any]]) -> None:
45
+ """Store messages in both Zep and MongoDB."""
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:
60
+ try:
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)
81
+ except Exception as e:
82
+ print(f"MongoDB storage error: {e}")
83
+
84
+ # Store in Zep
85
+ if not self.zep:
86
+ return
87
+
88
+ try:
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}")
97
+
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(
103
+ role=msg["role"],
104
+ content=msg["content"],
105
+ role_type=msg["role"],
106
+ )
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}")
118
+
119
+ async def retrieve(self, user_id: str) -> str:
120
+ """Retrieve memory context from Zep only."""
121
+ if not self.zep:
122
+ return ""
123
+
124
+ try:
125
+ memory = await self.zep.memory.get(session_id=user_id)
126
+ if memory is None or not hasattr(memory, 'context') or memory.context is None:
127
+ return ""
128
+ return memory.context
129
+
130
+ except Exception as e:
131
+ print(f"Error retrieving Zep memory: {e}")
132
+ return ""
133
+
134
+ async def delete(self, user_id: str) -> None:
135
+ """Delete memory from both systems."""
136
+ if self.mongo:
137
+ try:
138
+ self.mongo.delete_all(
139
+ self.collection,
140
+ {"user_id": user_id}
141
+ )
142
+ except Exception as e:
143
+ print(f"MongoDB deletion error: {e}")
144
+
145
+ if not self.zep:
146
+ return
147
+
148
+ try:
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:
154
+ await self.zep.user.delete(user_id=user_id)
155
+ except Exception as e:
156
+ print(f"Zep user deletion error: {e}")
157
+
158
+ def find(
159
+ self,
160
+ collection: str,
161
+ query: Dict,
162
+ sort: Optional[List[Tuple]] = None,
163
+ limit: int = 0,
164
+ skip: int = 0
165
+ ) -> List[Dict]: # pragma: no cover
166
+ """Find documents in MongoDB."""
167
+ if not self.mongo:
168
+ return []
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 []
175
+
176
+ def count_documents(self, collection: str, query: Dict) -> int:
177
+ """Count documents in MongoDB."""
178
+ if not self.mongo:
179
+ return 0
180
+ return self.mongo.count_documents(collection, query)
181
+
182
+ def _truncate(self, text: str, limit: int = 2500) -> str:
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
+
190
+ if len(text) <= limit:
191
+ return text
192
+
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]
197
+
198
+ # If no period found, truncate at limit and add ellipsis
199
+ return text[:limit] + "..."
@@ -13,6 +13,7 @@ from typing import AsyncGenerator, Dict, List, Literal, Optional, Any, Union
13
13
  from solana_agent.interfaces.services.agent import AgentService as AgentServiceInterface
14
14
  from solana_agent.interfaces.providers.llm import LLMProvider
15
15
  from solana_agent.interfaces.plugins.plugins import ToolRegistry as ToolRegistryInterface
16
+ from solana_agent.plugins.manager import PluginManager
16
17
  from solana_agent.plugins.registry import ToolRegistry
17
18
  from solana_agent.domains.agent import AIAgent, BusinessMission
18
19
 
@@ -40,8 +41,10 @@ class AgentService(AgentServiceInterface):
40
41
  self.tool_registry = ToolRegistry(config=self.config)
41
42
  self.agents: List[AIAgent] = []
42
43
 
43
- # Will be set by factory if plugin system is enabled
44
- self.plugin_manager = None
44
+ self.plugin_manager = PluginManager(
45
+ config=self.config,
46
+ tool_registry=self.tool_registry,
47
+ )
45
48
 
46
49
  def register_ai_agent(
47
50
  self, name: str, instructions: str, specialization: str,
@@ -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,154 +0,0 @@
1
- from typing import List, Dict, Any, Optional, Tuple
2
- from datetime import datetime, timezone
3
- from zep_cloud.client import AsyncZep as AsyncZepCloud
4
- from zep_python.client import AsyncZep
5
- from zep_cloud.types import Message
6
- from solana_agent.interfaces.providers.memory import MemoryProvider
7
- from solana_agent.adapters.mongodb_adapter import MongoDBAdapter
8
-
9
-
10
- class MemoryRepository(MemoryProvider):
11
- """Combined Zep and MongoDB implementation of MemoryProvider."""
12
-
13
- def __init__(
14
- self,
15
- mongo_adapter: Optional[MongoDBAdapter] = None,
16
- zep_api_key: Optional[str] = None,
17
- zep_base_url: Optional[str] = None
18
- ):
19
- """Initialize the combined memory provider."""
20
- if not mongo_adapter:
21
- self.mongo = None
22
- self.collection = None
23
- else:
24
- # Initialize MongoDB
25
- self.mongo = mongo_adapter
26
- self.collection = "conversations"
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)])
32
-
33
- # Initialize Zep
34
- if zep_api_key and not zep_base_url:
35
- self.zep = AsyncZepCloud(api_key=zep_api_key)
36
- elif zep_api_key and zep_base_url:
37
- self.zep = AsyncZep(api_key=zep_api_key, base_url=zep_base_url)
38
- else:
39
- self.zep = None
40
-
41
- async def store(self, user_id: str, messages: List[Dict[str, Any]]) -> None:
42
- """Store messages in both Zep and MongoDB."""
43
- # Store in MongoDB as single document
44
- if self.mongo:
45
- 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)
59
- except Exception as e:
60
- print(f"MongoDB storage error: {e}")
61
-
62
- # Store in Zep with role-based format
63
- if not self.zep:
64
- return
65
-
66
- 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
78
-
79
- zep_messages = [
80
- Message(
81
- role=msg["role"],
82
- role_type=msg["role"],
83
- content=self._truncate(msg["content"])
84
- )
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}")
90
-
91
- async def retrieve(self, user_id: str) -> str:
92
- """Retrieve memory context from Zep only."""
93
- if not self.zep:
94
- return ""
95
-
96
- try:
97
- memory = await self.zep.memory.get(session_id=user_id)
98
-
99
- return memory.context
100
-
101
- except Exception as e:
102
- print(f"Error retrieving Zep memory: {e}")
103
- return ""
104
-
105
- async def delete(self, user_id: str) -> None:
106
- """Delete memory from both systems."""
107
- if self.mongo:
108
- try:
109
- self.mongo.delete_all(
110
- self.collection,
111
- {"user_id": user_id}
112
- )
113
- except Exception as e:
114
- print(f"MongoDB deletion error: {e}")
115
-
116
- if not self.zep:
117
- return
118
-
119
- try:
120
- await self.zep.memory.delete(session_id=user_id)
121
- await self.zep.user.delete(user_id=user_id)
122
- except Exception as e:
123
- print(f"Zep deletion error: {e}")
124
-
125
- def find(
126
- self,
127
- collection: str,
128
- query: Dict,
129
- sort: Optional[List[Tuple]] = None,
130
- limit: int = 0,
131
- skip: int = 0
132
- ) -> List[Dict]:
133
- """Find documents matching query."""
134
- if not self.mongo:
135
- return []
136
- return self.mongo.find(collection, query, sort=sort, limit=limit, skip=skip)
137
-
138
- def count_documents(self, collection: str, query: Dict) -> int:
139
- """Count documents matching query."""
140
- if not self.mongo:
141
- return 0
142
- return self.mongo.count_documents(collection, query)
143
-
144
- def _truncate(self, text: str, limit: int = 2500) -> str:
145
- """Truncate text to be within limits."""
146
- if len(text) <= limit:
147
- return text
148
-
149
- truncated = text[:limit]
150
- last_period = truncated.rfind(".")
151
- if last_period > limit * 0.8:
152
- return truncated[:last_period + 1]
153
-
154
- return truncated + "..."
File without changes
File without changes