solana-agent 21.1.0__tar.gz → 22.0.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-21.1.0 → solana_agent-22.0.1}/PKG-INFO +30 -28
- {solana_agent-21.1.0 → solana_agent-22.0.1}/README.md +29 -26
- {solana_agent-21.1.0 → solana_agent-22.0.1}/pyproject.toml +1 -2
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/adapters/llm_adapter.py +4 -4
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/client/solana_agent.py +3 -3
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/factories/agent_factory.py +3 -4
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/client/client.py +1 -1
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/providers/llm.py +1 -1
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/services/agent.py +1 -1
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/services/query.py +1 -1
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/repositories/memory.py +6 -9
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/services/agent.py +144 -98
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/services/query.py +4 -4
- {solana_agent-21.1.0 → solana_agent-22.0.1}/LICENSE +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/adapters/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/adapters/mongodb_adapter.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/client/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/domains/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/domains/agent.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/domains/routing.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/factories/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/plugins/plugins.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/providers/data_storage.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/providers/memory.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/interfaces/services/routing.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/plugins/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/plugins/manager.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/plugins/registry.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/plugins/tools/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/plugins/tools/auto_tool.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/repositories/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/services/__init__.py +0 -0
- {solana_agent-21.1.0 → solana_agent-22.0.1}/solana_agent/services/routing.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: solana-agent
|
3
|
-
Version:
|
3
|
+
Version: 22.0.1
|
4
4
|
Summary: Agentic IQ
|
5
5
|
License: MIT
|
6
6
|
Keywords: ai,openai,ai agents,agi
|
@@ -18,7 +18,6 @@ Requires-Dist: openai (>=1.70.0,<2.0.0)
|
|
18
18
|
Requires-Dist: pydantic (>=2.11.2,<3.0.0)
|
19
19
|
Requires-Dist: pymongo (>=4.11.3,<5.0.0)
|
20
20
|
Requires-Dist: zep-cloud (>=2.9.0,<3.0.0)
|
21
|
-
Requires-Dist: zep-python (>=2.0.2,<3.0.0)
|
22
21
|
Project-URL: Documentation, https://docs.solana-agent.com
|
23
22
|
Project-URL: Repository, https://github.com/truemagic-coder/solana-agent
|
24
23
|
Description-Content-Type: text/markdown
|
@@ -50,7 +49,6 @@ Build your AI business in three lines of code!
|
|
50
49
|
* Extensible Tooling
|
51
50
|
* Simple Business Definition
|
52
51
|
* Tested & Secure
|
53
|
-
* Support for MCP Servers
|
54
52
|
* Built in Python
|
55
53
|
* Deployed by [CometHeart](https://cometheart.com) & [WalletBubbles](https://walletbubbles.com)
|
56
54
|
|
@@ -67,14 +65,13 @@ Build your AI business in three lines of code!
|
|
67
65
|
* Powerful tool integration using standard Python packages and/or inline tools
|
68
66
|
* Assigned tools are utilized by agents automatically and effectively
|
69
67
|
* Simple business definition using JSON
|
70
|
-
* Ability to access any MCP server via URL
|
71
68
|
|
72
69
|
## Stack
|
73
70
|
|
74
71
|
* [Python](https://python.org) - Programming Language
|
75
72
|
* [OpenAI](https://openai.com) - LLMs
|
76
73
|
* [MongoDB](https://mongodb.com) - Conversational History (optional)
|
77
|
-
* [Zep](https://getzep.com) - Conversational Memory (optional)
|
74
|
+
* [Zep Cloud](https://getzep.com) - Conversational Memory (optional)
|
78
75
|
|
79
76
|
## Installation
|
80
77
|
|
@@ -240,19 +237,26 @@ config = {
|
|
240
237
|
```python
|
241
238
|
config = {
|
242
239
|
"zep": {
|
243
|
-
"api_key": "your-zep-api-key",
|
244
|
-
"base_url": "your-zep-base-url", # not applicable if using Zep Cloud
|
240
|
+
"api_key": "your-zep-cloud-api-key",
|
245
241
|
},
|
246
242
|
}
|
247
243
|
```
|
248
244
|
|
249
|
-
|
245
|
+
### Disable Internet Searching
|
250
246
|
|
251
|
-
|
247
|
+
```python
|
248
|
+
async for response in solana_agent.process("user123", "Write me a poem.", internet_search=False):
|
249
|
+
print(response, end="")
|
250
|
+
```
|
251
|
+
|
252
|
+
## Tools
|
253
|
+
|
254
|
+
Tools can be used from plugins like Solana Agent Kit (sakit) or via custom inline tools. Tools available via plugins integrate automatically with Solana Agent.
|
255
|
+
|
256
|
+
### Plugin Usage Example
|
252
257
|
|
253
258
|
`pip install sakit`
|
254
259
|
|
255
|
-
### MCP
|
256
260
|
```python
|
257
261
|
from solana_agent import SolanaAgent
|
258
262
|
|
@@ -261,20 +265,18 @@ config = {
|
|
261
265
|
"api_key": "your-openai-api-key",
|
262
266
|
},
|
263
267
|
"tools": {
|
264
|
-
"
|
265
|
-
"
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
]
|
270
|
-
}
|
268
|
+
"search_internet": {
|
269
|
+
"api_key": "your-perplexity-key", # Required
|
270
|
+
"citations": True, # Optional, defaults to True
|
271
|
+
"model": "sonar" # Optional, defaults to "sonar"
|
272
|
+
},
|
271
273
|
},
|
272
274
|
"agents": [
|
273
275
|
{
|
274
276
|
"name": "research_specialist",
|
275
277
|
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
276
278
|
"specialization": "Research and knowledge synthesis",
|
277
|
-
"tools": ["
|
279
|
+
"tools": ["search_internet"],
|
278
280
|
},
|
279
281
|
{
|
280
282
|
"name": "customer_support",
|
@@ -286,15 +288,11 @@ config = {
|
|
286
288
|
|
287
289
|
solana_agent = SolanaAgent(config=config)
|
288
290
|
|
289
|
-
async for response in solana_agent.process("user123", "What are the latest AI developments?"):
|
291
|
+
async for response in solana_agent.process("user123", "What are the latest AI developments?", internet_search=False):
|
290
292
|
print(response, end="")
|
291
293
|
```
|
292
294
|
|
293
|
-
|
294
|
-
|
295
|
-
## Advanced
|
296
|
-
|
297
|
-
### Custom Inline Tools
|
295
|
+
### Custom Inline Tool Example
|
298
296
|
|
299
297
|
```python
|
300
298
|
from solana_agent import SolanaAgent
|
@@ -374,9 +372,11 @@ async for response in solana_agent.process("user123", "What are the latest AI de
|
|
374
372
|
print(response, end="")
|
375
373
|
```
|
376
374
|
|
377
|
-
|
375
|
+
## Training your Agents
|
376
|
+
|
377
|
+
Many use-cases for Solana Agent require training your agents on your company data.
|
378
378
|
|
379
|
-
|
379
|
+
This can be accomplished via runtime prompt injection. Integrations that work well with this method are KBs like Pinecone and FAQs.
|
380
380
|
|
381
381
|
```python
|
382
382
|
from solana_agent import SolanaAgent
|
@@ -401,11 +401,13 @@ config = {
|
|
401
401
|
|
402
402
|
solana_agent = SolanaAgent(config=config)
|
403
403
|
|
404
|
-
async for response in solana_agent.process("user123", "What are the latest AI developments?", "
|
404
|
+
async for response in solana_agent.process("user123", "What are the latest AI developments?", "This is my FAQ"):
|
405
405
|
print(response, end="")
|
406
406
|
```
|
407
407
|
|
408
|
-
|
408
|
+
## Custom Routing
|
409
|
+
|
410
|
+
In advanced cases like implementing a ticketing system on-top of Solana Agent - you can use your own router.
|
409
411
|
|
410
412
|
```python
|
411
413
|
from solana_agent import SolanaAgent
|
@@ -25,7 +25,6 @@ Build your AI business in three lines of code!
|
|
25
25
|
* Extensible Tooling
|
26
26
|
* Simple Business Definition
|
27
27
|
* Tested & Secure
|
28
|
-
* Support for MCP Servers
|
29
28
|
* Built in Python
|
30
29
|
* Deployed by [CometHeart](https://cometheart.com) & [WalletBubbles](https://walletbubbles.com)
|
31
30
|
|
@@ -42,14 +41,13 @@ Build your AI business in three lines of code!
|
|
42
41
|
* Powerful tool integration using standard Python packages and/or inline tools
|
43
42
|
* Assigned tools are utilized by agents automatically and effectively
|
44
43
|
* Simple business definition using JSON
|
45
|
-
* Ability to access any MCP server via URL
|
46
44
|
|
47
45
|
## Stack
|
48
46
|
|
49
47
|
* [Python](https://python.org) - Programming Language
|
50
48
|
* [OpenAI](https://openai.com) - LLMs
|
51
49
|
* [MongoDB](https://mongodb.com) - Conversational History (optional)
|
52
|
-
* [Zep](https://getzep.com) - Conversational Memory (optional)
|
50
|
+
* [Zep Cloud](https://getzep.com) - Conversational Memory (optional)
|
53
51
|
|
54
52
|
## Installation
|
55
53
|
|
@@ -215,19 +213,26 @@ config = {
|
|
215
213
|
```python
|
216
214
|
config = {
|
217
215
|
"zep": {
|
218
|
-
"api_key": "your-zep-api-key",
|
219
|
-
"base_url": "your-zep-base-url", # not applicable if using Zep Cloud
|
216
|
+
"api_key": "your-zep-cloud-api-key",
|
220
217
|
},
|
221
218
|
}
|
222
219
|
```
|
223
220
|
|
224
|
-
|
221
|
+
### Disable Internet Searching
|
225
222
|
|
226
|
-
|
223
|
+
```python
|
224
|
+
async for response in solana_agent.process("user123", "Write me a poem.", internet_search=False):
|
225
|
+
print(response, end="")
|
226
|
+
```
|
227
|
+
|
228
|
+
## Tools
|
229
|
+
|
230
|
+
Tools can be used from plugins like Solana Agent Kit (sakit) or via custom inline tools. Tools available via plugins integrate automatically with Solana Agent.
|
231
|
+
|
232
|
+
### Plugin Usage Example
|
227
233
|
|
228
234
|
`pip install sakit`
|
229
235
|
|
230
|
-
### MCP
|
231
236
|
```python
|
232
237
|
from solana_agent import SolanaAgent
|
233
238
|
|
@@ -236,20 +241,18 @@ config = {
|
|
236
241
|
"api_key": "your-openai-api-key",
|
237
242
|
},
|
238
243
|
"tools": {
|
239
|
-
"
|
240
|
-
"
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
]
|
245
|
-
}
|
244
|
+
"search_internet": {
|
245
|
+
"api_key": "your-perplexity-key", # Required
|
246
|
+
"citations": True, # Optional, defaults to True
|
247
|
+
"model": "sonar" # Optional, defaults to "sonar"
|
248
|
+
},
|
246
249
|
},
|
247
250
|
"agents": [
|
248
251
|
{
|
249
252
|
"name": "research_specialist",
|
250
253
|
"instructions": "You are an expert researcher who synthesizes complex information clearly.",
|
251
254
|
"specialization": "Research and knowledge synthesis",
|
252
|
-
"tools": ["
|
255
|
+
"tools": ["search_internet"],
|
253
256
|
},
|
254
257
|
{
|
255
258
|
"name": "customer_support",
|
@@ -261,15 +264,11 @@ config = {
|
|
261
264
|
|
262
265
|
solana_agent = SolanaAgent(config=config)
|
263
266
|
|
264
|
-
async for response in solana_agent.process("user123", "What are the latest AI developments?"):
|
267
|
+
async for response in solana_agent.process("user123", "What are the latest AI developments?", internet_search=False):
|
265
268
|
print(response, end="")
|
266
269
|
```
|
267
270
|
|
268
|
-
|
269
|
-
|
270
|
-
## Advanced
|
271
|
-
|
272
|
-
### Custom Inline Tools
|
271
|
+
### Custom Inline Tool Example
|
273
272
|
|
274
273
|
```python
|
275
274
|
from solana_agent import SolanaAgent
|
@@ -349,9 +348,11 @@ async for response in solana_agent.process("user123", "What are the latest AI de
|
|
349
348
|
print(response, end="")
|
350
349
|
```
|
351
350
|
|
352
|
-
|
351
|
+
## Training your Agents
|
352
|
+
|
353
|
+
Many use-cases for Solana Agent require training your agents on your company data.
|
353
354
|
|
354
|
-
|
355
|
+
This can be accomplished via runtime prompt injection. Integrations that work well with this method are KBs like Pinecone and FAQs.
|
355
356
|
|
356
357
|
```python
|
357
358
|
from solana_agent import SolanaAgent
|
@@ -376,11 +377,13 @@ config = {
|
|
376
377
|
|
377
378
|
solana_agent = SolanaAgent(config=config)
|
378
379
|
|
379
|
-
async for response in solana_agent.process("user123", "What are the latest AI developments?", "
|
380
|
+
async for response in solana_agent.process("user123", "What are the latest AI developments?", "This is my FAQ"):
|
380
381
|
print(response, end="")
|
381
382
|
```
|
382
383
|
|
383
|
-
|
384
|
+
## Custom Routing
|
385
|
+
|
386
|
+
In advanced cases like implementing a ticketing system on-top of Solana Agent - you can use your own router.
|
384
387
|
|
385
388
|
```python
|
386
389
|
from solana_agent import SolanaAgent
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "solana-agent"
|
3
|
-
version = "
|
3
|
+
version = "22.0.1"
|
4
4
|
description = "Agentic IQ"
|
5
5
|
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
|
6
6
|
license = "MIT"
|
@@ -27,7 +27,6 @@ openai = "^1.70.0"
|
|
27
27
|
pydantic = "^2.11.2"
|
28
28
|
pymongo = "^4.11.3"
|
29
29
|
zep-cloud = "^2.9.0"
|
30
|
-
zep-python = "^2.0.2"
|
31
30
|
|
32
31
|
[tool.poetry.group.dev.dependencies]
|
33
32
|
pytest = "^8.3.5"
|
@@ -20,7 +20,7 @@ class OpenAIAdapter(LLMProvider):
|
|
20
20
|
self.client = OpenAI(api_key=api_key)
|
21
21
|
self.parse_model = "gpt-4o-mini"
|
22
22
|
self.text_model = "gpt-4o-mini"
|
23
|
-
self.
|
23
|
+
self.internet_search_model = "gpt-4o-mini-search-preview"
|
24
24
|
self.transcription_model = "gpt-4o-mini-transcribe"
|
25
25
|
self.tts_model = "tts-1"
|
26
26
|
|
@@ -106,7 +106,7 @@ class OpenAIAdapter(LLMProvider):
|
|
106
106
|
self,
|
107
107
|
prompt: str,
|
108
108
|
system_prompt: str = "",
|
109
|
-
|
109
|
+
internet_search: bool = False,
|
110
110
|
) -> AsyncGenerator[str, None]: # pragma: no cover
|
111
111
|
"""Generate text from OpenAI models."""
|
112
112
|
messages = []
|
@@ -117,8 +117,8 @@ class OpenAIAdapter(LLMProvider):
|
|
117
117
|
messages.append({"role": "user", "content": prompt})
|
118
118
|
|
119
119
|
model = self.text_model
|
120
|
-
if
|
121
|
-
model = self.
|
120
|
+
if internet_search:
|
121
|
+
model = self.internet_search_model
|
122
122
|
|
123
123
|
# Prepare request parameters
|
124
124
|
request_params = {
|
@@ -56,7 +56,7 @@ class SolanaAgent(SolanaAgentInterface):
|
|
56
56
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
57
57
|
] = "mp4",
|
58
58
|
router: Optional[RoutingInterface] = None,
|
59
|
-
|
59
|
+
internet_search: bool = True,
|
60
60
|
) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
|
61
61
|
"""Process a user message and return the response stream.
|
62
62
|
|
@@ -70,7 +70,7 @@ class SolanaAgent(SolanaAgentInterface):
|
|
70
70
|
audio_output_format: Audio output format
|
71
71
|
audio_input_format: Audio input format
|
72
72
|
router: Optional routing service for processing
|
73
|
-
|
73
|
+
internet_search: Flag to use OpenAI Internet search
|
74
74
|
|
75
75
|
Returns:
|
76
76
|
Async generator yielding response chunks (text strings or audio bytes)
|
@@ -85,7 +85,7 @@ class SolanaAgent(SolanaAgentInterface):
|
|
85
85
|
audio_input_format=audio_input_format,
|
86
86
|
prompt=prompt,
|
87
87
|
router=router,
|
88
|
-
|
88
|
+
internet_search=internet_search,
|
89
89
|
):
|
90
90
|
yield chunk
|
91
91
|
|
@@ -74,17 +74,16 @@ class SolanaAgentFactory:
|
|
74
74
|
if "api_key" not in config["zep"]:
|
75
75
|
raise ValueError("Zep API key is required.")
|
76
76
|
memory_provider = MemoryRepository(
|
77
|
-
db_adapter, config["zep"].get("api_key")
|
77
|
+
mongo_adapter=db_adapter, zep_api_key=config["zep"].get("api_key"))
|
78
78
|
|
79
79
|
if "mongo" in config and not "zep" in config:
|
80
|
-
memory_provider = MemoryRepository(db_adapter)
|
80
|
+
memory_provider = MemoryRepository(mongo_adapter=db_adapter)
|
81
81
|
|
82
82
|
if "zep" in config and not "mongo" in config:
|
83
83
|
if "api_key" not in config["zep"]:
|
84
84
|
raise ValueError("Zep API key is required.")
|
85
85
|
memory_provider = MemoryRepository(
|
86
|
-
zep_api_key=config["zep"].get("api_key")
|
87
|
-
zep_base_url=config["zep"].get("base_url")
|
86
|
+
zep_api_key=config["zep"].get("api_key")
|
88
87
|
)
|
89
88
|
|
90
89
|
# Create primary services
|
@@ -24,7 +24,7 @@ class SolanaAgent(ABC):
|
|
24
24
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
25
25
|
] = "mp4",
|
26
26
|
router: Optional[RoutingInterface] = None,
|
27
|
-
|
27
|
+
internet_search: bool = True,
|
28
28
|
) -> AsyncGenerator[Union[str, bytes], None]:
|
29
29
|
"""Process a user message and return the response stream."""
|
30
30
|
pass
|
@@ -34,7 +34,7 @@ class AgentService(ABC):
|
|
34
34
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
35
35
|
] = "mp4",
|
36
36
|
prompt: Optional[str] = None,
|
37
|
-
|
37
|
+
internet_search: bool = True,
|
38
38
|
) -> AsyncGenerator[Union[str, bytes], None]:
|
39
39
|
"""Generate a response from an agent."""
|
40
40
|
pass
|
@@ -20,7 +20,7 @@ class QueryService(ABC):
|
|
20
20
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
21
21
|
] = "mp4",
|
22
22
|
prompt: Optional[str] = None,
|
23
|
-
|
23
|
+
internet_search: bool = True,
|
24
24
|
) -> AsyncGenerator[Union[str, bytes], None]:
|
25
25
|
"""Process the user request and generate a response."""
|
26
26
|
pass
|
@@ -1,7 +1,7 @@
|
|
1
|
+
from copy import deepcopy
|
1
2
|
from typing import List, Dict, Any, Optional, Tuple
|
2
3
|
from datetime import datetime, timezone
|
3
4
|
from zep_cloud.client import AsyncZep as AsyncZepCloud
|
4
|
-
from zep_python.client import AsyncZep
|
5
5
|
from zep_cloud.types import Message
|
6
6
|
from solana_agent.interfaces.providers.memory import MemoryProvider
|
7
7
|
from solana_agent.adapters.mongodb_adapter import MongoDBAdapter
|
@@ -14,7 +14,6 @@ class MemoryRepository(MemoryProvider):
|
|
14
14
|
self,
|
15
15
|
mongo_adapter: Optional[MongoDBAdapter] = None,
|
16
16
|
zep_api_key: Optional[str] = None,
|
17
|
-
zep_base_url: Optional[str] = None
|
18
17
|
):
|
19
18
|
"""Initialize the combined memory provider."""
|
20
19
|
if not mongo_adapter:
|
@@ -33,13 +32,10 @@ class MemoryRepository(MemoryProvider):
|
|
33
32
|
except Exception as e:
|
34
33
|
print(f"Error initializing MongoDB: {e}")
|
35
34
|
|
35
|
+
self.zep = None
|
36
36
|
# Initialize Zep
|
37
|
-
if zep_api_key
|
37
|
+
if zep_api_key:
|
38
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
39
|
|
44
40
|
async def store(self, user_id: str, messages: List[Dict[str, Any]]) -> None:
|
45
41
|
"""Store messages in both Zep and MongoDB."""
|
@@ -99,9 +95,10 @@ class MemoryRepository(MemoryProvider):
|
|
99
95
|
zep_messages = []
|
100
96
|
for msg in messages:
|
101
97
|
if "role" in msg and "content" in msg:
|
98
|
+
content = self._truncate(deepcopy(msg["content"]))
|
102
99
|
zep_msg = Message(
|
103
100
|
role=msg["role"],
|
104
|
-
content=
|
101
|
+
content=content,
|
105
102
|
role_type=msg["role"],
|
106
103
|
)
|
107
104
|
zep_messages.append(zep_msg)
|
@@ -196,4 +193,4 @@ class MemoryRepository(MemoryProvider):
|
|
196
193
|
return text[:last_period + 1]
|
197
194
|
|
198
195
|
# If no period found, truncate at limit and add ellipsis
|
199
|
-
return text[:limit] + "..."
|
196
|
+
return text[:limit-3] + "..."
|
@@ -176,31 +176,15 @@ class AgentService(AgentServiceInterface):
|
|
176
176
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
177
177
|
] = "mp4",
|
178
178
|
prompt: Optional[str] = None,
|
179
|
-
|
179
|
+
internet_search: bool = True,
|
180
180
|
) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
|
181
|
-
"""Generate a response with support for text/audio input/output.
|
182
|
-
|
183
|
-
Args:
|
184
|
-
agent_name: Agent name
|
185
|
-
user_id: User ID
|
186
|
-
query: Text query or audio bytes
|
187
|
-
memory_context: Optional conversation context
|
188
|
-
output_format: Response format ("text" or "audio")
|
189
|
-
audio_voice: Voice to use for audio output
|
190
|
-
audio_instructions: Optional instructions for audio synthesis
|
191
|
-
audio_output_format: Audio output format
|
192
|
-
audio_input_format: Audio input format
|
193
|
-
prompt: Optional prompt for the agent
|
194
|
-
use_openai_search: Flag to use OpenAI search
|
195
|
-
|
196
|
-
Yields:
|
197
|
-
Text chunks or audio bytes depending on output_format
|
198
|
-
"""
|
181
|
+
"""Generate a response with support for text/audio input/output."""
|
199
182
|
agent = next((a for a in self.agents if a.name == agent_name), None)
|
200
183
|
if not agent:
|
201
184
|
error_msg = f"Agent '{agent_name}' not found."
|
202
185
|
if output_format == "audio":
|
203
|
-
async for chunk in self.llm_provider.tts(error_msg, instructions=audio_instructions,
|
186
|
+
async for chunk in self.llm_provider.tts(error_msg, instructions=audio_instructions,
|
187
|
+
response_format=audio_output_format, voice=audio_voice):
|
204
188
|
yield chunk
|
205
189
|
else:
|
206
190
|
yield error_msg
|
@@ -229,20 +213,24 @@ class AgentService(AgentServiceInterface):
|
|
229
213
|
if prompt:
|
230
214
|
system_prompt += f"\n\nADDITIONAL PROMPT: {prompt}"
|
231
215
|
|
232
|
-
#
|
216
|
+
# Variables for tracking the response
|
233
217
|
complete_text_response = ""
|
218
|
+
|
219
|
+
# For audio output, we'll collect everything first
|
220
|
+
full_response_buffer = ""
|
221
|
+
|
222
|
+
# Variables for handling JSON processing
|
234
223
|
json_buffer = ""
|
235
224
|
is_json = False
|
236
|
-
text_buffer = ""
|
237
225
|
|
238
226
|
# Generate and stream response
|
239
227
|
async for chunk in self.llm_provider.generate_text(
|
240
228
|
prompt=query_text,
|
241
229
|
system_prompt=system_prompt,
|
242
|
-
|
230
|
+
internet_search=internet_search,
|
243
231
|
):
|
244
|
-
# Check
|
245
|
-
if chunk.strip().startswith("{") and not is_json:
|
232
|
+
# Check if the chunk is JSON or a tool call
|
233
|
+
if (chunk.strip().startswith("{") or "{\"tool_call\":" in chunk) and not is_json:
|
246
234
|
is_json = True
|
247
235
|
json_buffer = chunk
|
248
236
|
continue
|
@@ -256,107 +244,105 @@ class AgentService(AgentServiceInterface):
|
|
256
244
|
|
257
245
|
# Valid JSON found, handle it
|
258
246
|
if "tool_call" in data:
|
259
|
-
# Process tool call with existing method
|
260
247
|
response_text = await self._handle_tool_call(
|
261
248
|
agent_name=agent_name,
|
262
249
|
json_chunk=json_buffer
|
263
250
|
)
|
264
251
|
|
265
|
-
|
252
|
+
# Update system prompt to prevent further tool calls
|
253
|
+
tool_system_prompt = system_prompt + \
|
266
254
|
"\n DO NOT make any tool calls or return JSON."
|
267
255
|
|
256
|
+
# Create prompt with tool response
|
268
257
|
user_prompt = f"\n USER QUERY: {query_text} \n"
|
269
258
|
user_prompt += f"\n TOOL RESPONSE: {response_text} \n"
|
270
259
|
|
271
|
-
#
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
if output_format == "text":
|
260
|
+
# For text output, process chunks directly
|
261
|
+
if output_format == "text":
|
262
|
+
# Stream text response for text output
|
263
|
+
async for processed_chunk in self.llm_provider.generate_text(
|
264
|
+
prompt=user_prompt,
|
265
|
+
system_prompt=tool_system_prompt,
|
266
|
+
):
|
267
|
+
complete_text_response += processed_chunk
|
280
268
|
yield processed_chunk
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
async for audio_chunk in self.llm_provider.tts(
|
288
|
-
text=processed_text,
|
289
|
-
voice=audio_voice,
|
290
|
-
response_format=audio_output_format
|
269
|
+
else:
|
270
|
+
# For audio output, collect the full tool response first
|
271
|
+
tool_response = ""
|
272
|
+
async for processed_chunk in self.llm_provider.generate_text(
|
273
|
+
prompt=user_prompt,
|
274
|
+
system_prompt=tool_system_prompt,
|
291
275
|
):
|
292
|
-
|
276
|
+
tool_response += processed_chunk
|
277
|
+
|
278
|
+
# Add to our complete text record and full audio buffer
|
279
|
+
tool_response = self._remove_markdown(
|
280
|
+
tool_response)
|
281
|
+
complete_text_response += tool_response
|
282
|
+
full_response_buffer += tool_response
|
293
283
|
else:
|
294
284
|
# For non-tool JSON, still capture the text
|
295
285
|
complete_text_response += json_buffer
|
296
286
|
|
297
|
-
if output_format == "
|
298
|
-
async for audio_chunk in self.llm_provider.tts(
|
299
|
-
text=json_buffer,
|
300
|
-
voice=audio_voice,
|
301
|
-
response_format=audio_output_format
|
302
|
-
):
|
303
|
-
yield audio_chunk
|
304
|
-
else:
|
287
|
+
if output_format == "text":
|
305
288
|
yield json_buffer
|
289
|
+
else:
|
290
|
+
# Add to full response buffer for audio
|
291
|
+
full_response_buffer += json_buffer
|
306
292
|
|
307
293
|
# Reset JSON handling
|
308
294
|
is_json = False
|
309
295
|
json_buffer = ""
|
310
296
|
|
311
297
|
except json.JSONDecodeError:
|
298
|
+
# JSON not complete yet, continue collecting
|
312
299
|
pass
|
313
300
|
else:
|
314
|
-
# For regular text
|
301
|
+
# For regular text
|
315
302
|
complete_text_response += chunk
|
316
303
|
|
317
|
-
|
318
|
-
|
319
|
-
text_buffer += chunk
|
320
|
-
if any(punct in chunk for punct in ".!?"):
|
321
|
-
async for audio_chunk in self.llm_provider.tts(
|
322
|
-
text=text_buffer,
|
323
|
-
voice=audio_voice,
|
324
|
-
response_format=audio_output_format
|
325
|
-
):
|
326
|
-
yield audio_chunk
|
327
|
-
text_buffer = ""
|
328
|
-
else:
|
304
|
+
if output_format == "text":
|
305
|
+
# For text output, yield directly
|
329
306
|
yield chunk
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
if
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
# Add remaining text to complete response
|
340
|
-
complete_text_response += remaining_text
|
341
|
-
|
342
|
-
if output_format == "audio":
|
343
|
-
async for audio_chunk in self.llm_provider.tts(
|
344
|
-
text=remaining_text,
|
345
|
-
voice=audio_voice,
|
346
|
-
response_format=audio_output_format
|
347
|
-
):
|
348
|
-
yield audio_chunk
|
307
|
+
else:
|
308
|
+
# For audio output, add to the full response buffer
|
309
|
+
full_response_buffer += chunk
|
310
|
+
|
311
|
+
# Handle any leftover JSON buffer
|
312
|
+
if json_buffer:
|
313
|
+
complete_text_response += json_buffer
|
314
|
+
if output_format == "text":
|
315
|
+
yield json_buffer
|
349
316
|
else:
|
350
|
-
|
351
|
-
|
352
|
-
#
|
353
|
-
|
317
|
+
full_response_buffer += json_buffer
|
318
|
+
|
319
|
+
# For audio output, now process the complete response
|
320
|
+
if output_format == "audio" and full_response_buffer:
|
321
|
+
# Clean markdown before TTS
|
322
|
+
full_response_buffer = self._remove_markdown(
|
323
|
+
full_response_buffer)
|
324
|
+
|
325
|
+
# Process the entire response with TTS
|
326
|
+
async for audio_chunk in self.llm_provider.tts(
|
327
|
+
text=full_response_buffer,
|
328
|
+
voice=audio_voice,
|
329
|
+
response_format=audio_output_format,
|
330
|
+
instructions=audio_instructions
|
331
|
+
):
|
332
|
+
yield audio_chunk
|
333
|
+
|
334
|
+
# Store the complete text response
|
354
335
|
self.last_text_response = complete_text_response
|
355
336
|
|
356
337
|
except Exception as e:
|
357
338
|
error_msg = f"I apologize, but I encountered an error: {str(e)}"
|
358
339
|
if output_format == "audio":
|
359
|
-
async for chunk in self.llm_provider.tts(
|
340
|
+
async for chunk in self.llm_provider.tts(
|
341
|
+
error_msg,
|
342
|
+
voice=audio_voice,
|
343
|
+
response_format=audio_output_format,
|
344
|
+
instructions=audio_instructions
|
345
|
+
):
|
360
346
|
yield chunk
|
361
347
|
else:
|
362
348
|
yield error_msg
|
@@ -379,15 +365,31 @@ class AgentService(AgentServiceInterface):
|
|
379
365
|
parameters = tool_data.get("parameters", {})
|
380
366
|
|
381
367
|
if tool_name:
|
382
|
-
|
383
|
-
|
368
|
+
# Execute the tool and get the result
|
369
|
+
result = await self.execute_tool(agent_name, tool_name, parameters)
|
370
|
+
|
384
371
|
if result.get("status") == "success":
|
385
|
-
|
372
|
+
tool_result = result.get("result", "")
|
373
|
+
return tool_result
|
386
374
|
else:
|
387
|
-
|
388
|
-
|
389
|
-
|
375
|
+
error_message = f"I apologize, but I encountered an issue with the {tool_name} tool: {result.get('message', 'Unknown error')}"
|
376
|
+
print(f"Tool error: {error_message}")
|
377
|
+
return error_message
|
378
|
+
else:
|
379
|
+
return "Tool name was not provided in the tool call."
|
380
|
+
else:
|
381
|
+
print(f"JSON received but no tool_call found: {json_chunk}")
|
382
|
+
|
383
|
+
# If we get here, it wasn't properly handled as a tool
|
384
|
+
return f"The following request was not processed as a valid tool call:\n{json_chunk}"
|
385
|
+
except json.JSONDecodeError as e:
|
386
|
+
print(f"JSON decode error in tool call: {e}")
|
390
387
|
return json_chunk
|
388
|
+
except Exception as e:
|
389
|
+
print(f"Unexpected error in tool call handling: {str(e)}")
|
390
|
+
import traceback
|
391
|
+
print(traceback.format_exc())
|
392
|
+
return f"Error processing tool call: {str(e)}"
|
391
393
|
|
392
394
|
def _get_tool_usage_prompt(self, agent_name: str) -> str:
|
393
395
|
"""Generate JSON-based instructions for tool usage."""
|
@@ -424,3 +426,47 @@ class AgentService(AgentServiceInterface):
|
|
424
426
|
- No explanation text before or after
|
425
427
|
- Use exact tool names as shown in AVAILABLE TOOLS
|
426
428
|
"""
|
429
|
+
|
430
|
+
def _remove_markdown(self, text: str) -> str:
|
431
|
+
"""Remove Markdown formatting and links from text.
|
432
|
+
|
433
|
+
Args:
|
434
|
+
text: Input text with potential Markdown formatting
|
435
|
+
|
436
|
+
Returns:
|
437
|
+
Clean text without Markdown formatting
|
438
|
+
"""
|
439
|
+
import re
|
440
|
+
|
441
|
+
if not text:
|
442
|
+
return ""
|
443
|
+
|
444
|
+
# Remove Markdown links - [text](url) -> text
|
445
|
+
text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text)
|
446
|
+
|
447
|
+
# Remove inline code with backticks
|
448
|
+
text = re.sub(r'`([^`]+)`', r'\1', text)
|
449
|
+
|
450
|
+
# Remove bold formatting - **text** or __text__ -> text
|
451
|
+
text = re.sub(r'(\*\*|__)(.*?)\1', r'\2', text)
|
452
|
+
|
453
|
+
# Remove italic formatting - *text* or _text_ -> text
|
454
|
+
text = re.sub(r'(\*|_)(.*?)\1', r'\2', text)
|
455
|
+
|
456
|
+
# Remove headers - ## Header -> Header
|
457
|
+
text = re.sub(r'^\s*#+\s*(.*?)$', r'\1', text, flags=re.MULTILINE)
|
458
|
+
|
459
|
+
# Remove blockquotes - > Text -> Text
|
460
|
+
text = re.sub(r'^\s*>\s*(.*?)$', r'\1', text, flags=re.MULTILINE)
|
461
|
+
|
462
|
+
# Remove horizontal rules (---, ***, ___)
|
463
|
+
text = re.sub(r'^\s*[-*_]{3,}\s*$', '', text, flags=re.MULTILINE)
|
464
|
+
|
465
|
+
# Remove list markers - * Item or - Item or 1. Item -> Item
|
466
|
+
text = re.sub(r'^\s*[-*+]\s+(.*?)$', r'\1', text, flags=re.MULTILINE)
|
467
|
+
text = re.sub(r'^\s*\d+\.\s+(.*?)$', r'\1', text, flags=re.MULTILINE)
|
468
|
+
|
469
|
+
# Remove multiple consecutive newlines (keep just one)
|
470
|
+
text = re.sub(r'\n{3,}', '\n\n', text)
|
471
|
+
|
472
|
+
return text.strip()
|
@@ -49,7 +49,7 @@ class QueryService(QueryServiceInterface):
|
|
49
49
|
] = "mp4",
|
50
50
|
prompt: Optional[str] = None,
|
51
51
|
router: Optional[RoutingServiceInterface] = None,
|
52
|
-
|
52
|
+
internet_search: bool = True,
|
53
53
|
) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
|
54
54
|
"""Process the user request with appropriate agent.
|
55
55
|
|
@@ -63,7 +63,7 @@ class QueryService(QueryServiceInterface):
|
|
63
63
|
audio_input_format: Audio input format
|
64
64
|
prompt: Optional prompt for the agent
|
65
65
|
router: Optional routing service for processing
|
66
|
-
|
66
|
+
internet_search: Flag to use OpenAI Internet search
|
67
67
|
|
68
68
|
Yields:
|
69
69
|
Response chunks (text strings or audio bytes)
|
@@ -121,7 +121,7 @@ class QueryService(QueryServiceInterface):
|
|
121
121
|
audio_output_format=audio_output_format,
|
122
122
|
audio_instructions=audio_instructions,
|
123
123
|
prompt=prompt,
|
124
|
-
|
124
|
+
internet_search=internet_search,
|
125
125
|
):
|
126
126
|
yield audio_chunk
|
127
127
|
|
@@ -140,7 +140,7 @@ class QueryService(QueryServiceInterface):
|
|
140
140
|
memory_context=memory_context,
|
141
141
|
output_format="text",
|
142
142
|
prompt=prompt,
|
143
|
-
|
143
|
+
internet_search=internet_search,
|
144
144
|
):
|
145
145
|
yield chunk
|
146
146
|
full_text_response += chunk
|
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-21.1.0 → solana_agent-22.0.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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|