solana-agent 17.1.8__py3-none-any.whl → 17.1.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solana_agent/domains/agent.py +10 -2
- solana_agent/factories/agent_factory.py +1 -18
- solana_agent/repositories/memory.py +2 -2
- solana_agent/services/agent.py +118 -65
- solana_agent/services/query.py +2 -27
- solana_agent/services/routing.py +53 -22
- {solana_agent-17.1.8.dist-info → solana_agent-17.1.10.dist-info}/METADATA +2 -2
- {solana_agent-17.1.8.dist-info → solana_agent-17.1.10.dist-info}/RECORD +10 -12
- solana_agent/interfaces/repositories/agent.py +0 -33
- solana_agent/repositories/agent.py +0 -99
- {solana_agent-17.1.8.dist-info → solana_agent-17.1.10.dist-info}/LICENSE +0 -0
- {solana_agent-17.1.8.dist-info → solana_agent-17.1.10.dist-info}/WHEEL +0 -0
solana_agent/domains/agent.py
CHANGED
@@ -23,8 +23,8 @@ class OrganizationMission(BaseModel):
|
|
23
23
|
default_factory=list,
|
24
24
|
description="Organization goals"
|
25
25
|
)
|
26
|
-
|
27
|
-
None, description="
|
26
|
+
voice: str = Field(
|
27
|
+
None, description="Organization voice or tone")
|
28
28
|
|
29
29
|
@field_validator("mission_statement")
|
30
30
|
@classmethod
|
@@ -34,6 +34,14 @@ class OrganizationMission(BaseModel):
|
|
34
34
|
raise ValueError("Mission statement cannot be empty")
|
35
35
|
return v
|
36
36
|
|
37
|
+
@field_validator("voice")
|
38
|
+
@classmethod
|
39
|
+
def voice_not_empty(cls, v: str) -> str:
|
40
|
+
"""Validate that voice is not empty."""
|
41
|
+
if not v.strip():
|
42
|
+
raise ValueError("Voice cannot be empty")
|
43
|
+
return v
|
44
|
+
|
37
45
|
@field_validator("values")
|
38
46
|
@classmethod
|
39
47
|
def validate_values(cls, values: List[Dict[str, str]]) -> List[Dict[str, str]]:
|
@@ -13,7 +13,6 @@ from solana_agent.services.routing import RoutingService
|
|
13
13
|
|
14
14
|
# Repository imports
|
15
15
|
from solana_agent.repositories.memory import MemoryRepository
|
16
|
-
from solana_agent.repositories.agent import MongoAgentRepository
|
17
16
|
|
18
17
|
# Adapter imports
|
19
18
|
from solana_agent.adapters.llm_adapter import OpenAIAdapter
|
@@ -56,7 +55,7 @@ class SolanaAgentFactory:
|
|
56
55
|
values=[{"name": k, "description": v}
|
57
56
|
for k, v in org_config.get("values", {}).items()],
|
58
57
|
goals=org_config.get("goals", []),
|
59
|
-
|
58
|
+
voice=org_config.get("voice", "")
|
60
59
|
)
|
61
60
|
|
62
61
|
# Create repositories
|
@@ -65,11 +64,9 @@ class SolanaAgentFactory:
|
|
65
64
|
db_adapter, config["zep"].get("api_key"), config["zep"].get("base_url"))
|
66
65
|
else:
|
67
66
|
memory_provider = MemoryRepository(db_adapter)
|
68
|
-
agent_repo = MongoAgentRepository(db_adapter)
|
69
67
|
|
70
68
|
# Create primary services
|
71
69
|
agent_service = AgentService(
|
72
|
-
agent_repository=agent_repo,
|
73
70
|
llm_provider=llm_adapter,
|
74
71
|
organization_mission=organization_mission,
|
75
72
|
config=config,
|
@@ -93,20 +90,6 @@ class SolanaAgentFactory:
|
|
93
90
|
loaded_plugins = agent_service.plugin_manager.load_plugins()
|
94
91
|
print(f"Loaded {loaded_plugins} plugins")
|
95
92
|
|
96
|
-
# Sync MongoDB with config-defined agents
|
97
|
-
config_defined_agents = [agent["name"]
|
98
|
-
for agent in config.get("agents", [])]
|
99
|
-
all_db_agents = agent_repo.get_all_ai_agents()
|
100
|
-
db_agent_names = [agent.name for agent in all_db_agents]
|
101
|
-
|
102
|
-
# Delete agents not in config
|
103
|
-
agents_to_delete = [
|
104
|
-
name for name in db_agent_names if name not in config_defined_agents]
|
105
|
-
for agent_name in agents_to_delete:
|
106
|
-
print(
|
107
|
-
f"Deleting agent '{agent_name}' from MongoDB - no longer defined in config")
|
108
|
-
agent_repo.delete_ai_agent(agent_name)
|
109
|
-
|
110
93
|
# Register predefined agents
|
111
94
|
for agent_config in config.get("agents", []):
|
112
95
|
agent_service.register_ai_agent(
|
@@ -46,8 +46,8 @@ class MemoryRepository(MemoryProvider):
|
|
46
46
|
|
47
47
|
doc = {
|
48
48
|
"user_id": user_id,
|
49
|
-
"user_message":
|
50
|
-
"assistant_message":
|
49
|
+
"user_message": user_message,
|
50
|
+
"assistant_message": assistant_message,
|
51
51
|
"timestamp": datetime.now(timezone.utc)
|
52
52
|
}
|
53
53
|
self.mongo.insert_one(self.collection, doc)
|
solana_agent/services/agent.py
CHANGED
@@ -4,6 +4,7 @@ Agent service implementation.
|
|
4
4
|
This service manages AI and human agents, their registration, tool assignments,
|
5
5
|
and response generation.
|
6
6
|
"""
|
7
|
+
import asyncio
|
7
8
|
import datetime as main_datetime
|
8
9
|
from datetime import datetime
|
9
10
|
import json
|
@@ -11,7 +12,6 @@ from typing import AsyncGenerator, Dict, List, Literal, Optional, Any, Union
|
|
11
12
|
|
12
13
|
from solana_agent.interfaces.services.agent import AgentService as AgentServiceInterface
|
13
14
|
from solana_agent.interfaces.providers.llm import LLMProvider
|
14
|
-
from solana_agent.interfaces.repositories.agent import AgentRepository
|
15
15
|
from solana_agent.interfaces.plugins.plugins import ToolRegistry as ToolRegistryInterface
|
16
16
|
from solana_agent.plugins.registry import ToolRegistry
|
17
17
|
from solana_agent.domains.agent import AIAgent, OrganizationMission
|
@@ -23,7 +23,6 @@ class AgentService(AgentServiceInterface):
|
|
23
23
|
def __init__(
|
24
24
|
self,
|
25
25
|
llm_provider: LLMProvider,
|
26
|
-
agent_repository: AgentRepository,
|
27
26
|
organization_mission: Optional[OrganizationMission] = None,
|
28
27
|
config: Optional[Dict[str, Any]] = None,
|
29
28
|
):
|
@@ -31,16 +30,15 @@ class AgentService(AgentServiceInterface):
|
|
31
30
|
|
32
31
|
Args:
|
33
32
|
llm_provider: Provider for language model interactions
|
34
|
-
agent_repository: Repository for agent data
|
35
33
|
organization_mission: Optional organization mission and values
|
36
34
|
config: Optional service configuration
|
37
35
|
"""
|
38
36
|
self.llm_provider = llm_provider
|
39
|
-
self.agent_repository = agent_repository
|
40
37
|
self.organization_mission = organization_mission
|
41
38
|
self.config = config or {}
|
42
39
|
self.last_text_response = ""
|
43
40
|
self.tool_registry = ToolRegistry(config=self.config)
|
41
|
+
self.agents: List[AIAgent] = []
|
44
42
|
|
45
43
|
# Will be set by factory if plugin system is enabled
|
46
44
|
self.plugin_manager = None
|
@@ -60,7 +58,7 @@ class AgentService(AgentServiceInterface):
|
|
60
58
|
instructions=instructions,
|
61
59
|
specialization=specialization,
|
62
60
|
)
|
63
|
-
self.
|
61
|
+
self.agents.append(agent)
|
64
62
|
|
65
63
|
def get_agent_system_prompt(self, agent_name: str) -> str:
|
66
64
|
"""Get the system prompt for an agent.
|
@@ -71,7 +69,9 @@ class AgentService(AgentServiceInterface):
|
|
71
69
|
Returns:
|
72
70
|
System prompt
|
73
71
|
"""
|
74
|
-
|
72
|
+
|
73
|
+
# Get agent by name
|
74
|
+
agent = next((a for a in self.agents if a.name == agent_name), None)
|
75
75
|
|
76
76
|
# Build system prompt
|
77
77
|
system_prompt = f"You are {agent.name}, an AI assistant with the following instructions:\n\n"
|
@@ -83,6 +83,7 @@ class AgentService(AgentServiceInterface):
|
|
83
83
|
# Add mission and values if available
|
84
84
|
if self.organization_mission:
|
85
85
|
system_prompt += f"\n\nORGANIZATION MISSION:\n{self.organization_mission.mission_statement}"
|
86
|
+
system_prompt += f"\n\nVOICE OF THE BRAND:\n{self.organization_mission.voice}"
|
86
87
|
|
87
88
|
if self.organization_mission.values:
|
88
89
|
values_text = "\n".join([
|
@@ -91,11 +92,11 @@ class AgentService(AgentServiceInterface):
|
|
91
92
|
])
|
92
93
|
system_prompt += f"\n\nORGANIZATION VALUES:\n{values_text}"
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
95
|
+
# Add organization goals if available
|
96
|
+
if self.organization_mission.goals:
|
97
|
+
goals_text = "\n".join(
|
98
|
+
[f"- {goal}" for goal in self.organization_mission.goals])
|
99
|
+
system_prompt += f"\n\nORGANIZATION GOALS:\n{goals_text}"
|
99
100
|
|
100
101
|
return system_prompt
|
101
102
|
|
@@ -105,8 +106,7 @@ class AgentService(AgentServiceInterface):
|
|
105
106
|
Returns:
|
106
107
|
Dictionary mapping agent names to agents
|
107
108
|
"""
|
108
|
-
|
109
|
-
return {agent.name: agent for agent in agents}
|
109
|
+
return {agent.name: agent for agent in self.agents}
|
110
110
|
|
111
111
|
def get_specializations(self) -> Dict[str, str]:
|
112
112
|
"""Get all registered specializations.
|
@@ -116,9 +116,7 @@ class AgentService(AgentServiceInterface):
|
|
116
116
|
"""
|
117
117
|
specializations = {}
|
118
118
|
|
119
|
-
|
120
|
-
ai_agents = self.agent_repository.get_all_ai_agents()
|
121
|
-
for agent in ai_agents:
|
119
|
+
for agent in self.agents:
|
122
120
|
if agent.specialization:
|
123
121
|
specializations[agent.specialization] = f"AI expertise in {agent.specialization}"
|
124
122
|
|
@@ -148,16 +146,8 @@ class AgentService(AgentServiceInterface):
|
|
148
146
|
return self.tool_registry.get_agent_tools(agent_name)
|
149
147
|
|
150
148
|
def execute_tool(self, agent_name: str, tool_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
151
|
-
"""Execute a tool on behalf of an agent.
|
152
|
-
|
153
|
-
Args:
|
154
|
-
agent_name: Agent name
|
155
|
-
tool_name: Tool name
|
156
|
-
parameters: Tool parameters
|
149
|
+
"""Execute a tool on behalf of an agent."""
|
157
150
|
|
158
|
-
Returns:
|
159
|
-
Tool execution result
|
160
|
-
"""
|
161
151
|
if not self.tool_registry:
|
162
152
|
return {"status": "error", "message": "Tool registry not available"}
|
163
153
|
|
@@ -167,6 +157,7 @@ class AgentService(AgentServiceInterface):
|
|
167
157
|
|
168
158
|
# Check if agent has access to this tool
|
169
159
|
agent_tools = self.tool_registry.get_agent_tools(agent_name)
|
160
|
+
|
170
161
|
if not any(t.get("name") == tool_name for t in agent_tools):
|
171
162
|
return {
|
172
163
|
"status": "error",
|
@@ -174,8 +165,11 @@ class AgentService(AgentServiceInterface):
|
|
174
165
|
}
|
175
166
|
|
176
167
|
try:
|
177
|
-
|
168
|
+
result = tool.execute(**parameters)
|
169
|
+
return result
|
178
170
|
except Exception as e:
|
171
|
+
import traceback
|
172
|
+
print(traceback.format_exc())
|
179
173
|
return {"status": "error", "message": f"Error executing tool: {str(e)}"}
|
180
174
|
|
181
175
|
async def generate_response(
|
@@ -210,7 +204,7 @@ class AgentService(AgentServiceInterface):
|
|
210
204
|
Yields:
|
211
205
|
Text chunks or audio bytes depending on output_format
|
212
206
|
"""
|
213
|
-
agent = self.
|
207
|
+
agent = next((a for a in self.agents if a.name == agent_name), None)
|
214
208
|
if not agent:
|
215
209
|
error_msg = f"Agent '{agent_name}' not found."
|
216
210
|
if output_format == "audio":
|
@@ -247,6 +241,8 @@ class AgentService(AgentServiceInterface):
|
|
247
241
|
is_json = False
|
248
242
|
text_buffer = ""
|
249
243
|
|
244
|
+
print("\n=== Starting Response Generation ===")
|
245
|
+
|
250
246
|
# Generate and stream response
|
251
247
|
async for chunk in self.llm_provider.generate_text(
|
252
248
|
prompt=query_text,
|
@@ -264,28 +260,59 @@ class AgentService(AgentServiceInterface):
|
|
264
260
|
try:
|
265
261
|
# Try to parse complete JSON
|
266
262
|
data = json.loads(json_buffer)
|
263
|
+
print(
|
264
|
+
f"Successfully parsed JSON: {json.dumps(data, indent=2)}")
|
267
265
|
|
268
266
|
# Valid JSON found, handle it
|
269
|
-
if "
|
270
|
-
|
271
|
-
|
267
|
+
if "tool_calls" in data: # Now looking for tool_calls array
|
268
|
+
tool_results = []
|
269
|
+
async for tool_result in self._handle_multiple_tool_calls(
|
272
270
|
agent_name=agent_name,
|
273
271
|
json_chunk=json_buffer
|
274
|
-
)
|
272
|
+
):
|
273
|
+
tool_results.append(tool_result)
|
274
|
+
|
275
|
+
# Combine results and create a new prompt with clear instructions
|
276
|
+
tool_response = "\n".join(tool_results)
|
277
|
+
process_prompt = f"""
|
278
|
+
{tool_response}
|
279
|
+
|
280
|
+
IMPORTANT INSTRUCTIONS:
|
281
|
+
1. Maintain ALL factual details
|
282
|
+
2. Include ALL statistics, numbers, and specific data points
|
283
|
+
3. Use direct quotes where relevant
|
284
|
+
4. Keep ALL source citations and references
|
285
|
+
5. DO NOT omit or summarize away important details
|
286
|
+
6. DO NOT add any information not present in the results
|
287
|
+
7. DO NOT make any new tool calls or return JSON
|
288
|
+
"""
|
289
|
+
|
290
|
+
# Process combined results through LLM with modified system prompt
|
291
|
+
summary_system_prompt = self.get_agent_system_prompt(agent_name) + \
|
292
|
+
"\n DO NOT make any tool calls or return JSON. Present ALL facts and maintain ALL details from the source material."
|
293
|
+
|
294
|
+
# Collect all processed text first
|
295
|
+
processed_text = ""
|
296
|
+
async for processed_chunk in self.llm_provider.generate_text(
|
297
|
+
prompt=process_prompt,
|
298
|
+
system_prompt=summary_system_prompt,
|
299
|
+
):
|
300
|
+
processed_text += processed_chunk
|
301
|
+
# For text output, yield chunks as they come
|
302
|
+
if output_format == "text":
|
303
|
+
yield processed_chunk
|
275
304
|
|
276
|
-
# Add to complete
|
277
|
-
complete_text_response +=
|
305
|
+
# Add to complete response
|
306
|
+
complete_text_response += processed_text
|
278
307
|
|
279
|
-
#
|
308
|
+
# For audio output, process the complete text
|
280
309
|
if output_format == "audio":
|
281
310
|
async for audio_chunk in self.llm_provider.tts(
|
282
|
-
text=
|
311
|
+
text=processed_text,
|
283
312
|
voice=audio_voice,
|
284
313
|
response_format=audio_output_format
|
285
314
|
):
|
286
315
|
yield audio_chunk
|
287
|
-
else:
|
288
|
-
yield response_text
|
289
316
|
else:
|
290
317
|
# For non-tool JSON, still capture the text
|
291
318
|
complete_text_response += json_buffer
|
@@ -361,30 +388,6 @@ class AgentService(AgentServiceInterface):
|
|
361
388
|
import traceback
|
362
389
|
print(traceback.format_exc())
|
363
390
|
|
364
|
-
async def _handle_tool_call(
|
365
|
-
self,
|
366
|
-
agent_name: str,
|
367
|
-
json_chunk: str,
|
368
|
-
) -> str:
|
369
|
-
"""Handle tool calls and return formatted response."""
|
370
|
-
try:
|
371
|
-
data = json.loads(json_chunk)
|
372
|
-
if "tool_call" in data:
|
373
|
-
tool_data = data["tool_call"]
|
374
|
-
tool_name = tool_data.get("name")
|
375
|
-
parameters = tool_data.get("parameters", {})
|
376
|
-
|
377
|
-
if tool_name:
|
378
|
-
result = self.execute_tool(
|
379
|
-
agent_name, tool_name, parameters)
|
380
|
-
if result.get("status") == "success":
|
381
|
-
return result.get("result", "")
|
382
|
-
else:
|
383
|
-
return f"I apologize, but I encountered an issue: {result.get('message', 'Unknown error')}"
|
384
|
-
return json_chunk
|
385
|
-
except json.JSONDecodeError:
|
386
|
-
return json_chunk
|
387
|
-
|
388
391
|
def _get_tool_usage_prompt(self, agent_name: str) -> str:
|
389
392
|
"""Generate JSON-based instructions for tool usage."""
|
390
393
|
# Get tools assigned to this agent
|
@@ -402,12 +405,12 @@ class AgentService(AgentServiceInterface):
|
|
402
405
|
tool_example = """
|
403
406
|
For latest news query:
|
404
407
|
{
|
405
|
-
"
|
408
|
+
"tool_calls": [{
|
406
409
|
"name": "search_internet",
|
407
410
|
"parameters": {
|
408
411
|
"query": "latest Solana blockchain news March 2025"
|
409
412
|
}
|
410
|
-
}
|
413
|
+
}]
|
411
414
|
}"""
|
412
415
|
|
413
416
|
return f"""
|
@@ -416,12 +419,12 @@ class AgentService(AgentServiceInterface):
|
|
416
419
|
|
417
420
|
TOOL USAGE FORMAT:
|
418
421
|
{{
|
419
|
-
"
|
422
|
+
"tool_calls": [{{
|
420
423
|
"name": "<one_of:{', '.join(available_tool_names)}>",
|
421
424
|
"parameters": {{
|
422
425
|
// parameters as specified in tool definition above
|
423
426
|
}}
|
424
|
-
}}
|
427
|
+
}}]
|
425
428
|
}}
|
426
429
|
|
427
430
|
{tool_example if tool_example else ''}
|
@@ -437,3 +440,53 @@ class AgentService(AgentServiceInterface):
|
|
437
440
|
- No explanation text before or after
|
438
441
|
- Use exact tool names as shown in AVAILABLE TOOLS
|
439
442
|
"""
|
443
|
+
|
444
|
+
async def _handle_multiple_tool_calls(
|
445
|
+
self,
|
446
|
+
agent_name: str,
|
447
|
+
json_chunk: str,
|
448
|
+
) -> AsyncGenerator[str, None]:
|
449
|
+
"""Handle multiple tool calls concurrently and yield results as they complete."""
|
450
|
+
try:
|
451
|
+
data = json.loads(json_chunk)
|
452
|
+
if "tool_calls" not in data:
|
453
|
+
yield json_chunk
|
454
|
+
return
|
455
|
+
|
456
|
+
tool_calls = data["tool_calls"]
|
457
|
+
|
458
|
+
if not isinstance(tool_calls, list):
|
459
|
+
print("Error: tool_calls is not a list")
|
460
|
+
yield "Error: 'tool_calls' must be an array of tool calls."
|
461
|
+
return
|
462
|
+
|
463
|
+
# Define individual tool execution coroutine
|
464
|
+
async def execute_single_tool(tool_info):
|
465
|
+
tool_name = tool_info.get("name")
|
466
|
+
parameters = tool_info.get("parameters", {})
|
467
|
+
print(f"\nExecuting tool: {tool_name}")
|
468
|
+
print(f"With parameters: {parameters}")
|
469
|
+
|
470
|
+
if not tool_name:
|
471
|
+
return f"Error: Missing tool name in tool call."
|
472
|
+
|
473
|
+
result = self.execute_tool(agent_name, tool_name, parameters)
|
474
|
+
|
475
|
+
if result.get("status") == "success":
|
476
|
+
return f"Result from {tool_name}: {result.get('result', '')}"
|
477
|
+
else:
|
478
|
+
return f"Error from {tool_name}: {result.get('message', 'Unknown error')}"
|
479
|
+
|
480
|
+
# Execute all tool calls concurrently
|
481
|
+
tasks = [execute_single_tool(tool_call)
|
482
|
+
for tool_call in tool_calls]
|
483
|
+
for task in asyncio.as_completed(tasks):
|
484
|
+
result = await task
|
485
|
+
yield result
|
486
|
+
|
487
|
+
except json.JSONDecodeError:
|
488
|
+
yield "Error: Could not parse tool calls JSON."
|
489
|
+
except Exception as e:
|
490
|
+
import traceback
|
491
|
+
print(traceback.format_exc())
|
492
|
+
yield f"Error processing tool calls: {str(e)}"
|
solana_agent/services/query.py
CHANGED
@@ -268,37 +268,12 @@ class QueryService(QueryServiceInterface):
|
|
268
268
|
"""
|
269
269
|
if self.memory_provider:
|
270
270
|
try:
|
271
|
-
# Truncate excessively long responses
|
272
|
-
truncated_assistant_message = self._truncate(assistant_message)
|
273
|
-
truncated_user_message = self._truncate(user_message)
|
274
|
-
|
275
271
|
await self.memory_provider.store(
|
276
272
|
user_id,
|
277
273
|
[
|
278
|
-
{"role": "user", "content":
|
279
|
-
{"role": "assistant", "content":
|
274
|
+
{"role": "user", "content": user_message},
|
275
|
+
{"role": "assistant", "content": assistant_message},
|
280
276
|
],
|
281
277
|
)
|
282
278
|
except Exception as e:
|
283
279
|
print(f"Error storing conversation: {e}")
|
284
|
-
|
285
|
-
def _truncate(self, text: str, limit: int = 2500) -> str:
|
286
|
-
"""Truncate text to be within token limits.
|
287
|
-
|
288
|
-
Args:
|
289
|
-
text: Text to truncate
|
290
|
-
limit: Character limit
|
291
|
-
|
292
|
-
Returns:
|
293
|
-
Truncated text
|
294
|
-
"""
|
295
|
-
if len(text) <= limit:
|
296
|
-
return text
|
297
|
-
|
298
|
-
# Try to truncate at a sentence boundary
|
299
|
-
truncated = text[:limit]
|
300
|
-
last_period = truncated.rfind(".")
|
301
|
-
if last_period > limit * 0.8: # Only use period if reasonably close to the end
|
302
|
-
return truncated[:last_period + 1]
|
303
|
-
|
304
|
-
return truncated + "..."
|
solana_agent/services/routing.py
CHANGED
@@ -37,22 +37,43 @@ class RoutingService(RoutingServiceInterface):
|
|
37
37
|
Returns:
|
38
38
|
Analysis results including specializations and complexity
|
39
39
|
"""
|
40
|
+
# Get all available agents and their specializations
|
41
|
+
agents = self.agent_service.get_all_ai_agents()
|
42
|
+
available_specializations = []
|
43
|
+
|
44
|
+
for agent_id, agent in agents.items():
|
45
|
+
available_specializations.append({
|
46
|
+
"agent_name": agent_id,
|
47
|
+
"specialization": agent.specialization,
|
48
|
+
})
|
49
|
+
|
50
|
+
specializations_text = "\n".join([
|
51
|
+
f"- {spec['agent_name']}: {spec['specialization']}"
|
52
|
+
for spec in available_specializations
|
53
|
+
])
|
54
|
+
|
40
55
|
prompt = f"""
|
41
|
-
Analyze this user query and determine
|
42
|
-
|
43
|
-
|
56
|
+
Analyze this user query and determine which agent would be best suited to answer it.
|
57
|
+
|
58
|
+
AVAILABLE AGENTS AND THEIR SPECIALIZATIONS:
|
59
|
+
{specializations_text}
|
60
|
+
|
61
|
+
USER QUERY: {query}
|
62
|
+
|
63
|
+
Please determine:
|
64
|
+
1. Which agent is the primary best match for this query (must be one of the listed agents)
|
65
|
+
2. Any secondary agents that might be helpful (must be from the listed agents)
|
44
66
|
3. The complexity level (1-5, where 5 is most complex)
|
45
67
|
4. Any key topics or technologies mentioned
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
Be objective and thorough in your analysis.
|
68
|
+
|
69
|
+
Think carefully about whether the query is more technical/development-focused or more
|
70
|
+
financial/market-focused to match with the appropriate agent.
|
50
71
|
"""
|
51
72
|
|
52
73
|
try:
|
53
74
|
analysis = await self.llm_provider.parse_structured_output(
|
54
75
|
prompt=prompt,
|
55
|
-
system_prompt="
|
76
|
+
system_prompt="Match user queries to the most appropriate agent based on specializations.",
|
56
77
|
model_class=QueryAnalysis,
|
57
78
|
)
|
58
79
|
|
@@ -67,7 +88,7 @@ class RoutingService(RoutingServiceInterface):
|
|
67
88
|
print(f"Error analyzing query: {e}")
|
68
89
|
# Return default analysis on error
|
69
90
|
return {
|
70
|
-
"primary_specialization": "general",
|
91
|
+
"primary_specialization": list(agents.keys())[0] if agents else "general",
|
71
92
|
"secondary_specializations": [],
|
72
93
|
"complexity_level": 1,
|
73
94
|
"topics": [],
|
@@ -84,8 +105,10 @@ class RoutingService(RoutingServiceInterface):
|
|
84
105
|
Name of the best agent
|
85
106
|
"""
|
86
107
|
# If only one agent - use that agent
|
87
|
-
|
88
|
-
|
108
|
+
agents = self.agent_service.get_all_ai_agents()
|
109
|
+
if len(agents) == 1:
|
110
|
+
print(f"Only one agent available: {next(iter(agents.keys()))}")
|
111
|
+
return next(iter(agents.keys()))
|
89
112
|
|
90
113
|
# Analyze query
|
91
114
|
analysis = await self._analyze_query(query)
|
@@ -107,8 +130,8 @@ class RoutingService(RoutingServiceInterface):
|
|
107
130
|
"""Find the best AI agent for a query.
|
108
131
|
|
109
132
|
Args:
|
110
|
-
primary_specialization: Primary specialization
|
111
|
-
secondary_specializations: Secondary specializations
|
133
|
+
primary_specialization: Primary agent name or specialization
|
134
|
+
secondary_specializations: Secondary agent names or specializations
|
112
135
|
|
113
136
|
Returns:
|
114
137
|
Name of the best matching agent, or None if no match
|
@@ -118,33 +141,41 @@ class RoutingService(RoutingServiceInterface):
|
|
118
141
|
if not ai_agents:
|
119
142
|
return None
|
120
143
|
|
121
|
-
#
|
144
|
+
# First, check if primary_specialization is directly an agent name
|
145
|
+
if primary_specialization in ai_agents:
|
146
|
+
return primary_specialization
|
147
|
+
|
148
|
+
# If not a direct agent name match, use specialization matching
|
122
149
|
agent_scores = []
|
123
150
|
|
124
151
|
for agent_id, agent in ai_agents.items():
|
125
|
-
# Base score
|
126
152
|
score = 0
|
127
153
|
|
128
|
-
# Check
|
129
|
-
if agent.specialization.lower()
|
154
|
+
# Check for specialization match
|
155
|
+
if agent.specialization.lower() in primary_specialization.lower() or \
|
156
|
+
primary_specialization.lower() in agent.specialization.lower():
|
130
157
|
score += 10
|
131
158
|
|
132
159
|
# Check secondary specializations
|
133
|
-
|
134
|
-
|
135
|
-
if sec_spec
|
136
|
-
score +=
|
160
|
+
for sec_spec in secondary_specializations:
|
161
|
+
if sec_spec in ai_agents: # Direct agent name match
|
162
|
+
if sec_spec == agent_id:
|
163
|
+
score += 5
|
164
|
+
elif agent.specialization.lower() in sec_spec.lower() or \
|
165
|
+
sec_spec.lower() in agent.specialization.lower():
|
166
|
+
score += 3
|
137
167
|
|
138
168
|
agent_scores.append((agent_id, score))
|
139
169
|
|
140
170
|
# Sort by score
|
141
171
|
agent_scores.sort(key=lambda x: x[1], reverse=True)
|
172
|
+
print(f"Agent scores: {agent_scores}")
|
142
173
|
|
143
174
|
# Return the highest scoring agent, if any
|
144
175
|
if agent_scores and agent_scores[0][1] > 0:
|
145
176
|
return agent_scores[0][0]
|
146
177
|
|
147
|
-
# If no
|
178
|
+
# If no match found, return first agent as fallback
|
148
179
|
if ai_agents:
|
149
180
|
return next(iter(ai_agents.keys()))
|
150
181
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: solana-agent
|
3
|
-
Version: 17.1.
|
3
|
+
Version: 17.1.10
|
4
4
|
Summary: Agentic IQ
|
5
5
|
License: MIT
|
6
6
|
Keywords: ai,openai,ai agents,agi
|
@@ -77,7 +77,7 @@ config = {
|
|
77
77
|
"goals": [
|
78
78
|
"Empower users with great answers to their queries.",
|
79
79
|
],
|
80
|
-
"
|
80
|
+
"voice": "The voice of the brand is that of a research organization."
|
81
81
|
},
|
82
82
|
"mongo": {
|
83
83
|
"connection_string": "mongodb://localhost:27017",
|
@@ -5,17 +5,16 @@ solana_agent/adapters/mongodb_adapter.py,sha256=qqEFbY_v1XGyFXBmwd5HSXSSHnA9wWo-
|
|
5
5
|
solana_agent/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
solana_agent/client/solana_agent.py,sha256=Q9vnsoezsdhe6-T_tMb7Gr-697D1Bo2qIpr1-ytP1ak,5361
|
7
7
|
solana_agent/domains/__init__.py,sha256=HiC94wVPRy-QDJSSRywCRrhrFfTBeHjfi5z-QfZv46U,168
|
8
|
-
solana_agent/domains/agent.py,sha256=
|
8
|
+
solana_agent/domains/agent.py,sha256=9ztePCPDMmbEF9NAsblRs0JVQciU3IVKoUF1QYTct9U,2838
|
9
9
|
solana_agent/domains/routing.py,sha256=UDlgTjUoC9xIBVYu_dnf9-KG_bBgdEXAv_UtDOrYo0w,650
|
10
10
|
solana_agent/factories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
solana_agent/factories/agent_factory.py,sha256=
|
11
|
+
solana_agent/factories/agent_factory.py,sha256=SuFTob-cbbUqc9gm06Sole7wEcxXs3sOS57eW_V2IHc,4886
|
12
12
|
solana_agent/interfaces/__init__.py,sha256=IQs1WIM1FeKP1-kY2FEfyhol_dB-I-VAe2rD6jrVF6k,355
|
13
13
|
solana_agent/interfaces/client/client.py,sha256=2-YxrNH54aDYf68KYSLfFVBktAJkVCGG8TE76yzM8N8,1445
|
14
14
|
solana_agent/interfaces/plugins/plugins.py,sha256=zNrYTs-kO9RrFpFjKqPW8SDAlq3yeIsctJO7gFjuSHs,3326
|
15
15
|
solana_agent/interfaces/providers/data_storage.py,sha256=NqGeFvAzhz9rr-liLPRNCGjooB2EIhe-EVsMmX__b0M,1658
|
16
16
|
solana_agent/interfaces/providers/llm.py,sha256=Ay0-37ppAirGZdGS2LrDq0xAr_WY1_Gis84OXPGsaWs,1653
|
17
17
|
solana_agent/interfaces/providers/memory.py,sha256=oNOH8WZXVW8assDigIWZAWiwkxbpDiKupxA2RB6tQvQ,1010
|
18
|
-
solana_agent/interfaces/repositories/agent.py,sha256=r2MzVYOpEBVN00yqRxr3bUgWUgSwqoI1hRrdHhgFpFU,819
|
19
18
|
solana_agent/interfaces/services/agent.py,sha256=V1v4NPwWdNNJhrVdiAUuHEqIpfrobKjJatSIWNa6mdQ,2155
|
20
19
|
solana_agent/interfaces/services/query.py,sha256=bspnm-CN6zjRWnlFnkl34qo0EIW5m2TQR53NTEWMaq4,1271
|
21
20
|
solana_agent/interfaces/services/routing.py,sha256=gohkt5f9uYDLpu4iDVDk9yj8js9P56R6QHSIDNylgwA,438
|
@@ -25,13 +24,12 @@ solana_agent/plugins/registry.py,sha256=Z41sW_E8vObg16gA9gB7IrcH5mRGFJMeNC879zJU
|
|
25
24
|
solana_agent/plugins/tools/__init__.py,sha256=c0z7ij42gs94_VJrcn4Y8gUlTxMhsFNY6ahIsNswdLk,231
|
26
25
|
solana_agent/plugins/tools/auto_tool.py,sha256=Z3CcOzwdXpzciH-5yphhd9qt1b9owTxhwC-dYmPF6B0,1489
|
27
26
|
solana_agent/repositories/__init__.py,sha256=fP83w83CGzXLnSdq-C5wbw9EhWTYtqE2lQTgp46-X_4,163
|
28
|
-
solana_agent/repositories/
|
29
|
-
solana_agent/repositories/memory.py,sha256=DrhaVxlE-iAOmX0sfDCqgdPJauYvrnPd7rmVlf6_HGE,4822
|
27
|
+
solana_agent/repositories/memory.py,sha256=cDGoRz8FEkjwCE7j0XvA03-NL0TyROAt4_uwx288Th0,4790
|
30
28
|
solana_agent/services/__init__.py,sha256=ab_NXJmwYUCmCrCzuTlZ47bJZINW0Y0F5jfQ9OovidU,163
|
31
|
-
solana_agent/services/agent.py,sha256=
|
32
|
-
solana_agent/services/query.py,sha256=
|
33
|
-
solana_agent/services/routing.py,sha256=
|
34
|
-
solana_agent-17.1.
|
35
|
-
solana_agent-17.1.
|
36
|
-
solana_agent-17.1.
|
37
|
-
solana_agent-17.1.
|
29
|
+
solana_agent/services/agent.py,sha256=fHE5deFvIREDY-99QDxG_JtiHBcYB5ontvKvi5I3oF8,19542
|
30
|
+
solana_agent/services/query.py,sha256=qXrvzAyMqESdF8QD3xYaz2vyfR7ndLpsh2TahYQ-LYg,10414
|
31
|
+
solana_agent/services/routing.py,sha256=IPvBicgTYXqQ8iIRaatCsBGQVsOBGdAkq2i6U8hZlOY,6479
|
32
|
+
solana_agent-17.1.10.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
|
33
|
+
solana_agent-17.1.10.dist-info/METADATA,sha256=EWDPkKhVIo-z3nbbcYO5FHQwoSVAsMU5MZyhXbGE8f8,4693
|
34
|
+
solana_agent-17.1.10.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
35
|
+
solana_agent-17.1.10.dist-info/RECORD,,
|
@@ -1,33 +0,0 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
from typing import List, Optional
|
3
|
-
|
4
|
-
from solana_agent.domains.agent import AIAgent
|
5
|
-
|
6
|
-
|
7
|
-
class AgentRepository(ABC):
|
8
|
-
"""Interface for agent data access."""
|
9
|
-
|
10
|
-
@abstractmethod
|
11
|
-
def get_ai_agent_by_name(self, name: str) -> Optional[AIAgent]:
|
12
|
-
"""Get an AI agent by name."""
|
13
|
-
pass
|
14
|
-
|
15
|
-
@abstractmethod
|
16
|
-
def get_ai_agent(self, name: str) -> Optional[AIAgent]:
|
17
|
-
"""Get an AI agent by name."""
|
18
|
-
pass
|
19
|
-
|
20
|
-
@abstractmethod
|
21
|
-
def get_all_ai_agents(self) -> List[AIAgent]:
|
22
|
-
"""Get all AI agents."""
|
23
|
-
pass
|
24
|
-
|
25
|
-
@abstractmethod
|
26
|
-
def save_ai_agent(self, agent: AIAgent) -> bool:
|
27
|
-
"""Save an AI agent."""
|
28
|
-
pass
|
29
|
-
|
30
|
-
@abstractmethod
|
31
|
-
def delete_ai_agent(self, name: str) -> bool:
|
32
|
-
"""Delete an AI agent."""
|
33
|
-
pass
|
@@ -1,99 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
MongoDB implementation of the agent repository.
|
3
|
-
"""
|
4
|
-
from typing import List, Optional, Any
|
5
|
-
|
6
|
-
from solana_agent.domains.agent import AIAgent
|
7
|
-
from solana_agent.interfaces.repositories.agent import AgentRepository
|
8
|
-
|
9
|
-
|
10
|
-
class MongoAgentRepository(AgentRepository):
|
11
|
-
"""MongoDB implementation of the AgentRepository interface."""
|
12
|
-
|
13
|
-
def __init__(self, db_adapter):
|
14
|
-
"""Initialize the repository with a database adapter."""
|
15
|
-
self.db = db_adapter
|
16
|
-
self.ai_agents_collection = "agents"
|
17
|
-
|
18
|
-
# Ensure collections exist
|
19
|
-
self.db.create_collection(self.ai_agents_collection)
|
20
|
-
|
21
|
-
# Create indexes
|
22
|
-
self.db.create_index(self.ai_agents_collection,
|
23
|
-
[("name", 1)], unique=True)
|
24
|
-
|
25
|
-
def get_ai_agent_by_name(self, name: str) -> Optional[AIAgent]:
|
26
|
-
"""Get an AI agent by name.
|
27
|
-
|
28
|
-
Args:
|
29
|
-
name: The name of the AI agent
|
30
|
-
|
31
|
-
Returns:
|
32
|
-
The AI agent or None if not found
|
33
|
-
"""
|
34
|
-
# Query the AI agents collection for a document with matching name
|
35
|
-
doc = self.db.find_one(self.ai_agents_collection, {"name": name})
|
36
|
-
|
37
|
-
# If no document found, return None
|
38
|
-
if not doc:
|
39
|
-
return None
|
40
|
-
|
41
|
-
# Convert the document to an AIAgent domain model
|
42
|
-
try:
|
43
|
-
return AIAgent.model_validate(doc)
|
44
|
-
except Exception as e:
|
45
|
-
print(f"Error parsing AI agent with name {name}: {e}")
|
46
|
-
return None
|
47
|
-
|
48
|
-
def get_ai_agent(self, name: str) -> Optional[AIAgent]:
|
49
|
-
"""Get an AI agent by name."""
|
50
|
-
doc = self.db.find_one(self.ai_agents_collection, {"name": name})
|
51
|
-
if not doc:
|
52
|
-
return None
|
53
|
-
|
54
|
-
return AIAgent.model_validate(doc)
|
55
|
-
|
56
|
-
def get_all_ai_agents(self) -> List[AIAgent]:
|
57
|
-
"""Get all AI agents in the system.
|
58
|
-
|
59
|
-
Returns:
|
60
|
-
List of all AI agents
|
61
|
-
"""
|
62
|
-
# Query all documents from the AI agents collection
|
63
|
-
docs = self.db.find(self.ai_agents_collection, {})
|
64
|
-
|
65
|
-
# Convert each document to an AIAgent domain model
|
66
|
-
ai_agents = []
|
67
|
-
for doc in docs:
|
68
|
-
try:
|
69
|
-
agent = AIAgent.model_validate(doc)
|
70
|
-
ai_agents.append(agent)
|
71
|
-
except Exception as e:
|
72
|
-
# Log the error but continue processing other agents
|
73
|
-
print(f"Error parsing AI agent from database: {e}")
|
74
|
-
|
75
|
-
return ai_agents
|
76
|
-
|
77
|
-
def save_ai_agent(self, agent: AIAgent) -> bool:
|
78
|
-
"""Save an AI agent."""
|
79
|
-
doc = agent.model_dump()
|
80
|
-
|
81
|
-
# Check if agent already exists
|
82
|
-
existing = self.db.find_one(
|
83
|
-
self.ai_agents_collection, {"name": agent.name})
|
84
|
-
|
85
|
-
if existing:
|
86
|
-
# Update existing agent
|
87
|
-
return self.db.update_one(
|
88
|
-
self.ai_agents_collection,
|
89
|
-
{"name": agent.name},
|
90
|
-
{"$set": doc}
|
91
|
-
)
|
92
|
-
else:
|
93
|
-
# Create new agent
|
94
|
-
self.db.insert_one(self.ai_agents_collection, doc)
|
95
|
-
return True
|
96
|
-
|
97
|
-
def delete_ai_agent(self, name: str) -> bool:
|
98
|
-
"""Delete an AI agent."""
|
99
|
-
return self.db.delete_one(self.ai_agents_collection, {"name": name})
|
File without changes
|
File without changes
|