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.
- {solana_agent-19.0.2 → solana_agent-19.1.1}/PKG-INFO +42 -26
- {solana_agent-19.0.2 → solana_agent-19.1.1}/README.md +37 -23
- {solana_agent-19.0.2 → solana_agent-19.1.1}/pyproject.toml +10 -4
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/adapters/llm_adapter.py +0 -13
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/client/solana_agent.py +12 -20
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/factories/agent_factory.py +41 -22
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/client/client.py +3 -2
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/providers/llm.py +0 -5
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/services/agent.py +3 -7
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/services/query.py +1 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/manager.py +29 -30
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/registry.py +20 -3
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/tools/auto_tool.py +2 -0
- solana_agent-19.1.1/solana_agent/repositories/memory.py +199 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/services/agent.py +10 -17
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/services/query.py +11 -4
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/services/routing.py +1 -1
- solana_agent-19.0.2/solana_agent/repositories/memory.py +0 -143
- {solana_agent-19.0.2 → solana_agent-19.1.1}/LICENSE +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/adapters/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/adapters/mongodb_adapter.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/client/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/domains/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/domains/agent.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/domains/routing.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/factories/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/plugins/plugins.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/providers/data_storage.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/providers/memory.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/services/routing.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/plugins/tools/__init__.py +0 -0
- {solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/repositories/__init__.py +0 -0
- {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.
|
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.
|
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
|
-
"
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
"
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
"
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
"
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
"
|
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.
|
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
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
91
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
121
|
-
|
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
|
-
#
|
39
|
-
self._plugins[plugin.name] = plugin
|
40
|
-
|
41
|
-
# Initialize the plugin with the tool registry
|
38
|
+
# Initialize the plugin with the tool registry first
|
42
39
|
plugin.initialize(self.tool_registry)
|
43
40
|
|
44
|
-
#
|
45
|
-
print(f"Configuring plugin {plugin.name} with config")
|
41
|
+
# Then configure the plugin
|
46
42
|
plugin.configure(self.config)
|
47
43
|
|
44
|
+
# Only store plugin if both initialize and configure succeed
|
45
|
+
self._plugins[plugin.name] = plugin
|
46
|
+
print(f"Successfully registered plugin {plugin.name}")
|
48
47
|
return True
|
48
|
+
|
49
49
|
except Exception as e:
|
50
50
|
print(f"Error registering plugin {plugin.name}: {e}")
|
51
|
+
# Remove plugin from registry if it was added
|
52
|
+
self._plugins.pop(plugin.name, None)
|
51
53
|
return False
|
52
54
|
|
53
55
|
def load_plugins(self) -> List[str]:
|
@@ -59,30 +61,27 @@ class PluginManager(PluginManagerInterface):
|
|
59
61
|
loaded_plugins = []
|
60
62
|
|
61
63
|
# Discover plugins through entry points
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
print(f"Error loading plugin {entry_point.name}: {e}")
|
84
|
-
except Exception as e:
|
85
|
-
print(f"Error discovering plugins: {e}")
|
64
|
+
for entry_point in importlib.metadata.entry_points(group='solana_agent.plugins'):
|
65
|
+
# Skip if this entry point has already been loaded
|
66
|
+
entry_point_id = f"{entry_point.name}:{entry_point.value}"
|
67
|
+
if entry_point_id in PluginManager._loaded_entry_points:
|
68
|
+
print(
|
69
|
+
f"Skipping already loaded plugin: {entry_point.name}")
|
70
|
+
continue
|
71
|
+
|
72
|
+
try:
|
73
|
+
print(f"Found plugin entry point: {entry_point.name}")
|
74
|
+
PluginManager._loaded_entry_points.add(entry_point_id)
|
75
|
+
plugin_factory = entry_point.load()
|
76
|
+
plugin = plugin_factory()
|
77
|
+
|
78
|
+
# Register the plugin
|
79
|
+
if self.register_plugin(plugin):
|
80
|
+
# Use entry_point.name instead of plugin.name
|
81
|
+
loaded_plugins.append(entry_point.name)
|
82
|
+
|
83
|
+
except Exception as e:
|
84
|
+
print(f"Error loading plugin {entry_point.name}: {e}")
|
86
85
|
|
87
86
|
return loaded_plugins
|
88
87
|
|
@@ -76,6 +76,23 @@ class ToolRegistry(ToolRegistryInterface):
|
|
76
76
|
return list(self._tools.keys())
|
77
77
|
|
78
78
|
def configure_all_tools(self, config: Dict[str, Any]) -> None:
|
79
|
-
"""Configure all registered tools with
|
80
|
-
|
81
|
-
|
79
|
+
"""Configure all registered tools with new configuration.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
config: Configuration dictionary to apply
|
83
|
+
"""
|
84
|
+
self._config.update(config)
|
85
|
+
configure_errors = []
|
86
|
+
|
87
|
+
for name, tool in self._tools.items():
|
88
|
+
try:
|
89
|
+
print(f"Configuring tool: {name}")
|
90
|
+
tool.configure(self._config)
|
91
|
+
except Exception as e:
|
92
|
+
print(f"Error configuring tool {name}: {e}")
|
93
|
+
configure_errors.append((name, str(e)))
|
94
|
+
|
95
|
+
if configure_errors:
|
96
|
+
print("The following tools failed to configure:")
|
97
|
+
for name, error in configure_errors:
|
98
|
+
print(f"- {name}: {error}")
|
@@ -34,6 +34,8 @@ class AutoTool(Tool):
|
|
34
34
|
|
35
35
|
def configure(self, config: Dict[str, Any]) -> None:
|
36
36
|
"""Configure the tool with settings from config."""
|
37
|
+
if config is None:
|
38
|
+
raise TypeError("Config cannot be None")
|
37
39
|
self._config = config
|
38
40
|
|
39
41
|
def get_schema(self) -> Dict[str, Any]:
|
@@ -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
|
-
|
44
|
-
|
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\
|
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
|
-
|
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
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{solana_agent-19.0.2 → solana_agent-19.1.1}/solana_agent/interfaces/providers/data_storage.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|