solana-agent 19.0.2__tar.gz → 19.1.1__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.0.2 → solana_agent-19.1.1}/PKG-INFO +42 -26
  2. {solana_agent-19.0.2 → solana_agent-19.1.1}/README.md +37 -23
  3. {solana_agent-19.0.2 → solana_agent-19.1.1}/pyproject.toml +10 -4
  4. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/adapters/llm_adapter.py +0 -13
  5. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/client/solana_agent.py +12 -20
  6. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/factories/agent_factory.py +41 -22
  7. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/client/client.py +3 -2
  8. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/providers/llm.py +0 -5
  9. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/services/agent.py +3 -7
  10. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/services/query.py +1 -0
  11. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/manager.py +29 -30
  12. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/registry.py +20 -3
  13. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/tools/auto_tool.py +2 -0
  14. solana_agent-19.1.1/solana_agent/repositories/memory.py +199 -0
  15. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/services/agent.py +10 -17
  16. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/services/query.py +11 -4
  17. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/services/routing.py +1 -1
  18. solana_agent-19.0.2/solana_agent/repositories/memory.py +0 -143
  19. {solana_agent-19.0.2 → solana_agent-19.1.1}/LICENSE +0 -0
  20. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/__init__.py +0 -0
  21. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/adapters/__init__.py +0 -0
  22. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/adapters/mongodb_adapter.py +0 -0
  23. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/client/__init__.py +0 -0
  24. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/domains/__init__.py +0 -0
  25. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/domains/agent.py +0 -0
  26. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/domains/routing.py +0 -0
  27. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/factories/__init__.py +0 -0
  28. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/__init__.py +0 -0
  29. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/plugins/plugins.py +0 -0
  30. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/providers/data_storage.py +0 -0
  31. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/providers/memory.py +0 -0
  32. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/services/routing.py +0 -0
  33. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/__init__.py +0 -0
  34. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/tools/__init__.py +0 -0
  35. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/repositories/__init__.py +0 -0
  36. {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/services/__init__.py +0 -0
@@ -1,23 +1,25 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solana-agent
3
- Version: 19.0.2
3
+ Version: 19.1.1
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
17
  Requires-Dist: openai (>=1.68.2,<2.0.0)
17
- Requires-Dist: pydantic (>=2.10.6,<3.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
 
@@ -64,7 +66,7 @@ Build your AI business in three lines of code!
64
66
 
65
67
  * [Python](https://python.org) - Programming Language
66
68
  * [OpenAI](https://openai.com) - LLMs
67
- * [MongoDB](https://mongodb.com) - Conversational History
69
+ * [MongoDB](https://mongodb.com) - Conversational History (optional)
68
70
  * [Zep](https://getzep.com) - Conversational Memory (optional)
69
71
 
70
72
  ## Installation
@@ -90,17 +92,17 @@ config = {
90
92
  ],
91
93
  "voice": "The voice of the brand is that of a research business."
92
94
  },
93
- "mongo": {
95
+ "mongo": { # optional
94
96
  "connection_string": "mongodb://localhost:27017",
95
97
  "database": "solana_agent"
96
98
  },
97
- "openai": {
98
- "api_key": "your-openai-api-key",
99
- },
100
99
  "zep": { # optional
101
100
  "api_key": "your-zep-api-key",
102
101
  "base_url": "your-zep-base-url", # not applicable if using Zep Cloud
103
102
  },
103
+ "openai": {
104
+ "api_key": "your-openai-api-key",
105
+ },
104
106
  "agents": [
105
107
  {
106
108
  "name": "research_specialist",
@@ -142,23 +144,26 @@ config = {
142
144
  ],
143
145
  "voice": "The voice of the brand is that of a research business."
144
146
  },
145
- "mongo": {
147
+ "openai": { # optional
148
+ "api_key": "your-openai-api-key",
149
+ },
150
+ "ollama": { # optional
151
+ "url": "your-ollama-url",
152
+ },
153
+ "mongo": { # optional
146
154
  "connection_string": "mongodb://localhost:27017",
147
155
  "database": "solana_agent"
148
156
  },
149
- "openai": {
150
- "api_key": "your-openai-api-key",
151
- },
152
157
  "zep": { # optional
153
158
  "api_key": "your-zep-api-key",
154
159
  "base_url": "your-zep-base-url", # not applicable if using Zep Cloud
155
160
  },
156
161
  "tools": {
157
- "search_internet": {
158
- "api_key": "your-perplexity-key", # Required
159
- "citations": True, # Optional, defaults to True
160
- "model": "sonar" # Optional, defaults to "sonar"
161
- },
162
+ "search_internet": {
163
+ "api_key": "your-perplexity-key", # Required
164
+ "citations": True, # Optional, defaults to True
165
+ "model": "sonar" # Optional, defaults to "sonar"
166
+ },
162
167
  },
163
168
  "agents": [
164
169
  {
@@ -247,13 +252,16 @@ config = {
247
252
  ],
248
253
  "voice": "The voice of the brand is that of a research business."
249
254
  },
250
- "mongo": {
255
+ "openai": { # optional
256
+ "api_key": "your-openai-api-key",
257
+ },
258
+ "ollama": { # optional
259
+ "url": "your-ollama-url",
260
+ },
261
+ "mongo": { # optional
251
262
  "connection_string": "mongodb://localhost:27017",
252
263
  "database": "solana_agent"
253
264
  },
254
- "openai": {
255
- "api_key": "your-openai-api-key",
256
- },
257
265
  "zep": { # optional
258
266
  "api_key": "your-zep-api-key",
259
267
  "base_url": "your-zep-base-url", # not applicable if using Zep Cloud
@@ -282,24 +290,32 @@ async for response in solana_agent.process("user123", "What are the latest AI de
282
290
  print(response, end="")
283
291
  ```
284
292
 
285
- ## Notes on Tools
293
+ ## Notes
286
294
  * Solana Agent agents can only call one tool per response.
287
295
  * Solana Agent agents choose the best tool for the job.
288
296
  * Solana Agent tools do not use OpenAI function calling.
289
297
  * Solana Agent tools are async functions.
298
+ * Solana Agent will use OpenAI for audio and Ollama and for text if both config vars are set
299
+
300
+ ## Local Setup
301
+
302
+ A Docker Compose and Zep Config file is available at the root of this project
290
303
 
291
304
  ## API Documentation
292
- * Available at [Solana Agent Documentation Site](https://docs.solana-agent.com)
293
305
 
294
- ## Solana Agent Kit
306
+ The official up-to-date documentation site
307
+
308
+ [Solana Agent Documentation Site](https://docs.solana-agent.com)
309
+
310
+ ## Official Tools
295
311
 
296
- A collection of Solana Agent tools
312
+ The official collection of tools in one plugin
297
313
 
298
314
  [Solana Agent Kit](https://github.com/truemagic-coder/solana-agent-kit)
299
315
 
300
316
  ## Example App
301
317
 
302
- A Solana Agent example app written in FastAPI and Next.js
318
+ The official example app written in FastAPI and Next.js
303
319
 
304
320
  [Solana Agent Example App](https://github.com/truemagic-coder/solana-agent-app)
305
321
 
@@ -41,7 +41,7 @@ Build your AI business in three lines of code!
41
41
 
42
42
  * [Python](https://python.org) - Programming Language
43
43
  * [OpenAI](https://openai.com) - LLMs
44
- * [MongoDB](https://mongodb.com) - Conversational History
44
+ * [MongoDB](https://mongodb.com) - Conversational History (optional)
45
45
  * [Zep](https://getzep.com) - Conversational Memory (optional)
46
46
 
47
47
  ## Installation
@@ -67,17 +67,17 @@ config = {
67
67
  ],
68
68
  "voice": "The voice of the brand is that of a research business."
69
69
  },
70
- "mongo": {
70
+ "mongo": { # optional
71
71
  "connection_string": "mongodb://localhost:27017",
72
72
  "database": "solana_agent"
73
73
  },
74
- "openai": {
75
- "api_key": "your-openai-api-key",
76
- },
77
74
  "zep": { # optional
78
75
  "api_key": "your-zep-api-key",
79
76
  "base_url": "your-zep-base-url", # not applicable if using Zep Cloud
80
77
  },
78
+ "openai": {
79
+ "api_key": "your-openai-api-key",
80
+ },
81
81
  "agents": [
82
82
  {
83
83
  "name": "research_specialist",
@@ -119,23 +119,26 @@ config = {
119
119
  ],
120
120
  "voice": "The voice of the brand is that of a research business."
121
121
  },
122
- "mongo": {
122
+ "openai": { # optional
123
+ "api_key": "your-openai-api-key",
124
+ },
125
+ "ollama": { # optional
126
+ "url": "your-ollama-url",
127
+ },
128
+ "mongo": { # optional
123
129
  "connection_string": "mongodb://localhost:27017",
124
130
  "database": "solana_agent"
125
131
  },
126
- "openai": {
127
- "api_key": "your-openai-api-key",
128
- },
129
132
  "zep": { # optional
130
133
  "api_key": "your-zep-api-key",
131
134
  "base_url": "your-zep-base-url", # not applicable if using Zep Cloud
132
135
  },
133
136
  "tools": {
134
- "search_internet": {
135
- "api_key": "your-perplexity-key", # Required
136
- "citations": True, # Optional, defaults to True
137
- "model": "sonar" # Optional, defaults to "sonar"
138
- },
137
+ "search_internet": {
138
+ "api_key": "your-perplexity-key", # Required
139
+ "citations": True, # Optional, defaults to True
140
+ "model": "sonar" # Optional, defaults to "sonar"
141
+ },
139
142
  },
140
143
  "agents": [
141
144
  {
@@ -224,13 +227,16 @@ config = {
224
227
  ],
225
228
  "voice": "The voice of the brand is that of a research business."
226
229
  },
227
- "mongo": {
230
+ "openai": { # optional
231
+ "api_key": "your-openai-api-key",
232
+ },
233
+ "ollama": { # optional
234
+ "url": "your-ollama-url",
235
+ },
236
+ "mongo": { # optional
228
237
  "connection_string": "mongodb://localhost:27017",
229
238
  "database": "solana_agent"
230
239
  },
231
- "openai": {
232
- "api_key": "your-openai-api-key",
233
- },
234
240
  "zep": { # optional
235
241
  "api_key": "your-zep-api-key",
236
242
  "base_url": "your-zep-base-url", # not applicable if using Zep Cloud
@@ -259,24 +265,32 @@ async for response in solana_agent.process("user123", "What are the latest AI de
259
265
  print(response, end="")
260
266
  ```
261
267
 
262
- ## Notes on Tools
268
+ ## Notes
263
269
  * Solana Agent agents can only call one tool per response.
264
270
  * Solana Agent agents choose the best tool for the job.
265
271
  * Solana Agent tools do not use OpenAI function calling.
266
272
  * Solana Agent tools are async functions.
273
+ * Solana Agent will use OpenAI for audio and Ollama and for text if both config vars are set
274
+
275
+ ## Local Setup
276
+
277
+ A Docker Compose and Zep Config file is available at the root of this project
267
278
 
268
279
  ## API Documentation
269
- * Available at [Solana Agent Documentation Site](https://docs.solana-agent.com)
270
280
 
271
- ## Solana Agent Kit
281
+ The official up-to-date documentation site
282
+
283
+ [Solana Agent Documentation Site](https://docs.solana-agent.com)
284
+
285
+ ## Official Tools
272
286
 
273
- A collection of Solana Agent tools
287
+ The official collection of tools in one plugin
274
288
 
275
289
  [Solana Agent Kit](https://github.com/truemagic-coder/solana-agent-kit)
276
290
 
277
291
  ## Example App
278
292
 
279
- A Solana Agent example app written in FastAPI and Next.js
293
+ The official example app written in FastAPI and Next.js
280
294
 
281
295
  [Solana Agent Example App](https://github.com/truemagic-coder/solana-agent-app)
282
296
 
@@ -1,14 +1,18 @@
1
1
  [tool.poetry]
2
2
  name = "solana-agent"
3
- version = "19.0.2"
3
+ version = "19.1.1"
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" }]
@@ -19,20 +23,22 @@ python_paths = [".", "tests"]
19
23
  [tool.poetry.dependencies]
20
24
  python = ">=3.12,<4.0"
21
25
  openai = "^1.68.2"
22
- pydantic = "^2.10.6"
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"]
@@ -135,19 +135,6 @@ class OpenAIAdapter(LLMProvider):
135
135
  print(traceback.format_exc())
136
136
  yield f"I apologize, but I encountered an error: {str(e)}"
137
137
 
138
- def generate_embedding(self, text: str) -> List[float]: # pragma: no cover
139
- """Generate embeddings for a given text using OpenAI's embedding model."""
140
- try:
141
- response = self.client.embeddings.create(
142
- model="text-embedding-3-small",
143
- input=text
144
- )
145
- return response.data[0].embedding
146
- except Exception as e:
147
- print(f"Error generating embedding: {e}")
148
- # Return a zero vector as fallback (not ideal but prevents crashing)
149
- return [0.0] * 1536 # Standard size for text-embedding-3-small
150
-
151
138
  async def parse_structured_output(
152
139
  self,
153
140
  prompt: str,
@@ -44,6 +44,7 @@ class SolanaAgent(SolanaAgentInterface):
44
44
  self,
45
45
  user_id: str,
46
46
  message: Union[str, bytes],
47
+ prompt: Optional[str] = None,
47
48
  output_format: Literal["text", "audio"] = "text",
48
49
  audio_voice: Literal["alloy", "ash", "ballad", "coral", "echo",
49
50
  "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
@@ -59,6 +60,7 @@ class SolanaAgent(SolanaAgentInterface):
59
60
  Args:
60
61
  user_id: User ID
61
62
  message: Text message or audio bytes
63
+ prompt: Optional prompt for the agent
62
64
  output_format: Response format ("text" or "audio")
63
65
  audio_voice: Voice to use for audio output
64
66
  audio_instructions: Optional instructions for audio synthesis
@@ -76,6 +78,7 @@ class SolanaAgent(SolanaAgentInterface):
76
78
  audio_instructions=audio_instructions,
77
79
  audio_output_format=audio_output_format,
78
80
  audio_input_format=audio_input_format,
81
+ prompt=prompt,
79
82
  ):
80
83
  yield chunk
81
84
 
@@ -94,7 +97,7 @@ class SolanaAgent(SolanaAgentInterface):
94
97
  page_num: int = 1,
95
98
  page_size: int = 20,
96
99
  sort_order: str = "desc" # "asc" for oldest-first, "desc" for newest-first
97
- ) -> Dict[str, Any]:
100
+ ) -> Dict[str, Any]: # pragma: no cover
98
101
  """
99
102
  Get paginated message history for a user.
100
103
 
@@ -121,22 +124,11 @@ class SolanaAgent(SolanaAgentInterface):
121
124
  Returns:
122
125
  True if successful, False
123
126
  """
124
-
125
- try:
126
- print(f"Attempting to register tool: {tool.name}")
127
- success = self.query_service.agent_service.tool_registry.register_tool(
128
- tool)
129
- if success:
130
- print(f"Tool {tool.name} registered successfully")
131
- # Get all agents and assign the tool to them
132
- agents = self.query_service.agent_service.get_all_ai_agents()
133
- for agent_name in agents:
134
- print(f"Assigning {tool.name} to agent {agent_name}")
135
- self.query_service.agent_service.assign_tool_for_agent(
136
- agent_name, tool.name)
137
- return success
138
- except Exception as e:
139
- print(f"Error in register_tool: {str(e)}")
140
- import traceback
141
- print(traceback.format_exc())
142
- 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
@@ -37,10 +37,19 @@ class SolanaAgentFactory:
37
37
  Configured QueryService instance
38
38
  """
39
39
  # Create adapters
40
- db_adapter = MongoDBAdapter(
41
- connection_string=config["mongo"]["connection_string"],
42
- database_name=config["mongo"]["database"],
43
- )
40
+
41
+ if "mongo" in config:
42
+ # MongoDB connection string and database name
43
+ if "connection_string" not in config["mongo"]:
44
+ raise ValueError("MongoDB connection string is required.")
45
+ if "database" not in config["mongo"]:
46
+ raise ValueError("MongoDB database name is required.")
47
+ db_adapter = MongoDBAdapter(
48
+ connection_string=config["mongo"]["connection_string"],
49
+ database_name=config["mongo"]["database"],
50
+ )
51
+ else:
52
+ db_adapter = None
44
53
 
45
54
  llm_adapter = OpenAIAdapter(
46
55
  api_key=config["openai"]["api_key"],
@@ -59,12 +68,25 @@ class SolanaAgentFactory:
59
68
  )
60
69
 
61
70
  # Create repositories
62
- if "zep" in config:
71
+ memory_provider = None
72
+
73
+ if "zep" in config and "mongo" in config:
74
+ if "api_key" not in config["zep"]:
75
+ raise ValueError("Zep API key is required.")
63
76
  memory_provider = MemoryRepository(
64
77
  db_adapter, config["zep"].get("api_key"), config["zep"].get("base_url"))
65
- else:
78
+
79
+ if "mongo" in config and not "zep" in config:
66
80
  memory_provider = MemoryRepository(db_adapter)
67
81
 
82
+ if "zep" in config and not "mongo" in config:
83
+ if "api_key" not in config["zep"]:
84
+ raise ValueError("Zep API key is required.")
85
+ memory_provider = MemoryRepository(
86
+ zep_api_key=config["zep"].get("api_key"),
87
+ zep_base_url=config["zep"].get("base_url")
88
+ )
89
+
68
90
  # Create primary services
69
91
  agent_service = AgentService(
70
92
  llm_provider=llm_adapter,
@@ -87,8 +109,12 @@ class SolanaAgentFactory:
87
109
  config=config,
88
110
  tool_registry=agent_service.tool_registry
89
111
  )
90
- loaded_plugins = agent_service.plugin_manager.load_plugins()
91
- 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
92
118
 
93
119
  # Register predefined agents
94
120
  for agent_config in config.get("agents", []):
@@ -103,25 +129,18 @@ class SolanaAgentFactory:
103
129
  for tool_name in agent_config["tools"]:
104
130
  print(
105
131
  f"Available tools before registering {tool_name}: {agent_service.tool_registry.list_all_tools()}")
106
- try:
107
- agent_service.assign_tool_for_agent(
108
- agent_config["name"], tool_name
109
- )
110
- print(
111
- f"Successfully registered {tool_name} for agent {agent_config['name']}")
112
- except ValueError as e:
113
- print(
114
- 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']}")
115
137
 
116
138
  # Global tool registrations
117
139
  if "agent_tools" in config:
118
140
  for agent_name, tools in config["agent_tools"].items():
119
141
  for tool_name in tools:
120
- try:
121
- agent_service.assign_tool_for_agent(
122
- agent_name, tool_name)
123
- except ValueError as e:
124
- print(f"Error registering tool: {e}")
142
+ agent_service.assign_tool_for_agent(
143
+ agent_name, tool_name)
125
144
 
126
145
  # Create and return the query service
127
146
  query_service = QueryService(
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Any, AsyncGenerator, Dict, Literal, Union
2
+ from typing import Any, AsyncGenerator, Dict, Literal, Optional, Union
3
3
 
4
4
  from solana_agent.interfaces.plugins.plugins import Tool
5
5
 
@@ -15,12 +15,13 @@ class SolanaAgent(ABC):
15
15
  output_format: Literal["text", "audio"] = "text",
16
16
  audio_voice: Literal["alloy", "ash", "ballad", "coral", "echo",
17
17
  "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
18
- audio_instructions: str = None,
18
+ audio_instructions: Optional[str] = None,
19
19
  audio_output_format: Literal['mp3', 'opus',
20
20
  'aac', 'flac', 'wav', 'pcm'] = "aac",
21
21
  audio_input_format: Literal[
22
22
  "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
23
23
  ] = "mp4",
24
+ prompt: Optional[str] = None,
24
25
  ) -> AsyncGenerator[Union[str, bytes], None]:
25
26
  """Process a user message and return the response stream."""
26
27
  pass
@@ -19,11 +19,6 @@ class LLMProvider(ABC):
19
19
  """Generate text from the language model."""
20
20
  pass
21
21
 
22
- @abstractmethod
23
- def generate_embedding(self, text: str) -> List[float]:
24
- """Generate embedding vector for text."""
25
- pass
26
-
27
22
  @abstractmethod
28
23
  async def parse_structured_output(
29
24
  self, prompt: str, system_prompt: str, model_class: Type[T],
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Any, AsyncGenerator, Dict, List, Literal, Union
2
+ from typing import Any, AsyncGenerator, Dict, List, Literal, Optional, Union
3
3
 
4
4
  from solana_agent.domains.agent import AIAgent
5
5
 
@@ -17,11 +17,6 @@ class AgentService(ABC):
17
17
  """Get the system prompt for an agent."""
18
18
  pass
19
19
 
20
- @abstractmethod
21
- def get_specializations(self) -> Dict[str, str]:
22
- """Get all registered specializations."""
23
- pass
24
-
25
20
  @abstractmethod
26
21
  async def generate_response(
27
22
  self,
@@ -32,12 +27,13 @@ class AgentService(ABC):
32
27
  output_format: Literal["text", "audio"] = "text",
33
28
  audio_voice: Literal["alloy", "ash", "ballad", "coral", "echo",
34
29
  "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
35
- audio_instructions: str = None,
30
+ audio_instructions: Optional[str] = None,
36
31
  audio_output_format: Literal['mp3', 'opus',
37
32
  'aac', 'flac', 'wav', 'pcm'] = "aac",
38
33
  audio_input_format: Literal[
39
34
  "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
40
35
  ] = "mp4",
36
+ prompt: Optional[str] = None,
41
37
  ) -> AsyncGenerator[Union[str, bytes], None]:
42
38
  """Generate a response from an agent."""
43
39
  pass
@@ -19,6 +19,7 @@ class QueryService(ABC):
19
19
  audio_input_format: Literal[
20
20
  "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
21
21
  ] = "mp4",
22
+ prompt: Optional[str] = None,
22
23
  ) -> AsyncGenerator[Union[str, bytes], None]:
23
24
  """Process the user request and generate a response."""
24
25
  pass
@@ -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,
@@ -108,20 +111,6 @@ class AgentService(AgentServiceInterface):
108
111
  """
109
112
  return {agent.name: agent for agent in self.agents}
110
113
 
111
- def get_specializations(self) -> Dict[str, str]:
112
- """Get all registered specializations.
113
-
114
- Returns:
115
- Dictionary mapping specialization names to descriptions
116
- """
117
- specializations = {}
118
-
119
- for agent in self.agents:
120
- if agent.specialization:
121
- specializations[agent.specialization] = f"AI expertise in {agent.specialization}"
122
-
123
- return specializations
124
-
125
114
  def assign_tool_for_agent(self, agent_name: str, tool_name: str) -> bool:
126
115
  """Assign a tool to an agent.
127
116
 
@@ -187,6 +176,7 @@ class AgentService(AgentServiceInterface):
187
176
  audio_input_format: Literal[
188
177
  "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
189
178
  ] = "mp4",
179
+ prompt: Optional[str] = None,
190
180
  ) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
191
181
  """Generate a response with support for text/audio input/output.
192
182
 
@@ -200,6 +190,7 @@ class AgentService(AgentServiceInterface):
200
190
  audio_instructions: Optional instructions for audio synthesis
201
191
  audio_output_format: Audio output format
202
192
  audio_input_format: Audio input format
193
+ prompt: Optional prompt for the agent
203
194
 
204
195
  Yields:
205
196
  Text chunks or audio bytes depending on output_format
@@ -233,7 +224,9 @@ class AgentService(AgentServiceInterface):
233
224
  # Add User ID and memory context
234
225
  system_prompt += f"\n\nUser ID: {user_id}"
235
226
  if memory_context:
236
- system_prompt += f"\n\nMemory Context: {memory_context}"
227
+ system_prompt += f"\n\nMEMORY CONTEXT: {memory_context}"
228
+ if prompt:
229
+ system_prompt += f"\n\nADDITIONAL PROMPT: {prompt}"
237
230
 
238
231
  # Keep track of the complete text response
239
232
  complete_text_response = ""
@@ -46,14 +46,19 @@ class QueryService(QueryServiceInterface):
46
46
  audio_input_format: Literal[
47
47
  "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
48
48
  ] = "mp4",
49
+ prompt: Optional[str] = None,
49
50
  ) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
50
51
  """Process the user request with appropriate agent.
51
52
 
52
53
  Args:
53
54
  user_id: User ID
54
55
  query: Text query or audio bytes
55
- output_format: Response format ("text" or "audio")
56
- voice: Voice to use for audio output
56
+ output_format: Response format ("text" or "audio")
57
+ audio_voice: Voice for TTS (text-to-speech)
58
+ audio_instructions: Optional instructions for TTS
59
+ audio_output_format: Audio output format
60
+ audio_input_format: Audio input format
61
+ prompt: Optional prompt for the agent
57
62
 
58
63
  Yields:
59
64
  Response chunks (text strings or audio bytes)
@@ -105,7 +110,8 @@ class QueryService(QueryServiceInterface):
105
110
  audio_voice=audio_voice,
106
111
  audio_input_format=audio_input_format,
107
112
  audio_output_format=audio_output_format,
108
- audio_instructions=audio_instructions
113
+ audio_instructions=audio_instructions,
114
+ prompt=prompt,
109
115
  ):
110
116
  yield audio_chunk
111
117
 
@@ -122,7 +128,8 @@ class QueryService(QueryServiceInterface):
122
128
  user_id=user_id,
123
129
  query=user_text,
124
130
  memory_context=memory_context,
125
- output_format="text"
131
+ output_format="text",
132
+ prompt=prompt,
126
133
  ):
127
134
  yield chunk
128
135
  full_text_response += chunk
@@ -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,143 +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: MongoDBAdapter,
16
- zep_api_key: Optional[str] = None,
17
- zep_base_url: Optional[str] = None
18
- ):
19
- """Initialize the combined memory provider."""
20
- # Initialize MongoDB
21
- self.mongo = mongo_adapter
22
- self.collection = "conversations"
23
-
24
- # Ensure MongoDB collection and indexes
25
- self.mongo.create_collection(self.collection)
26
- self.mongo.create_index(self.collection, [("user_id", 1)])
27
- self.mongo.create_index(self.collection, [("timestamp", 1)])
28
-
29
- # Initialize Zep
30
- if zep_api_key and not zep_base_url:
31
- self.zep = AsyncZepCloud(api_key=zep_api_key)
32
- elif zep_api_key and zep_base_url:
33
- self.zep = AsyncZep(api_key=zep_api_key, base_url=zep_base_url)
34
- else:
35
- self.zep = None
36
-
37
- async def store(self, user_id: str, messages: List[Dict[str, Any]]) -> None:
38
- """Store messages in both Zep and MongoDB."""
39
- # Store in MongoDB as single document
40
- try:
41
- # Extract user and assistant messages
42
- user_message = next(msg["content"]
43
- for msg in messages if msg["role"] == "user")
44
- assistant_message = next(
45
- msg["content"] for msg in messages if msg["role"] == "assistant")
46
-
47
- doc = {
48
- "user_id": user_id,
49
- "user_message": user_message,
50
- "assistant_message": assistant_message,
51
- "timestamp": datetime.now(timezone.utc)
52
- }
53
- self.mongo.insert_one(self.collection, doc)
54
- except Exception as e:
55
- print(f"MongoDB storage error: {e}")
56
-
57
- # Store in Zep with role-based format
58
- if not self.zep:
59
- return
60
-
61
- try:
62
- try:
63
- await self.zep.user.add(user_id=user_id)
64
- except Exception:
65
- pass
66
- try:
67
- await self.zep.memory.add_session(
68
- session_id=user_id,
69
- user_id=user_id,
70
- )
71
- except Exception:
72
- pass
73
-
74
- zep_messages = [
75
- Message(
76
- role=msg["role"],
77
- role_type=msg["role"],
78
- content=self._truncate(msg["content"])
79
- )
80
- for msg in messages
81
- ]
82
- await self.zep.memory.add(session_id=user_id, messages=zep_messages)
83
- except Exception as e:
84
- print(f"Zep storage error: {e}")
85
-
86
- async def retrieve(self, user_id: str) -> str:
87
- """Retrieve memory context from Zep only."""
88
- if not self.zep:
89
- return ""
90
-
91
- try:
92
- memory = await self.zep.memory.get(session_id=user_id)
93
-
94
- return memory.context
95
-
96
- except Exception as e:
97
- print(f"Error retrieving Zep memory: {e}")
98
- return ""
99
-
100
- async def delete(self, user_id: str) -> None:
101
- """Delete memory from both systems."""
102
- try:
103
- self.mongo.delete_all(
104
- self.collection,
105
- {"user_id": user_id}
106
- )
107
- except Exception as e:
108
- print(f"MongoDB deletion error: {e}")
109
-
110
- if not self.zep:
111
- return
112
-
113
- try:
114
- await self.zep.memory.delete(session_id=user_id)
115
- await self.zep.user.delete(user_id=user_id)
116
- except Exception as e:
117
- print(f"Zep deletion error: {e}")
118
-
119
- def find(
120
- self,
121
- collection: str,
122
- query: Dict,
123
- sort: Optional[List[Tuple]] = None,
124
- limit: int = 0,
125
- skip: int = 0
126
- ) -> List[Dict]:
127
- """Find documents matching query."""
128
- return self.mongo.find(collection, query, sort=sort, limit=limit, skip=skip)
129
-
130
- def count_documents(self, collection: str, query: Dict) -> int:
131
- return self.mongo.count_documents(collection, query)
132
-
133
- def _truncate(self, text: str, limit: int = 2500) -> str:
134
- """Truncate text to be within limits."""
135
- if len(text) <= limit:
136
- return text
137
-
138
- truncated = text[:limit]
139
- last_period = truncated.rfind(".")
140
- if last_period > limit * 0.8:
141
- return truncated[:last_period + 1]
142
-
143
- return truncated + "..."
File without changes