solana-agent 17.1.7__py3-none-any.whl → 17.1.9__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.
@@ -23,8 +23,8 @@ class OrganizationMission(BaseModel):
23
23
  default_factory=list,
24
24
  description="Organization goals"
25
25
  )
26
- guidance: Optional[str] = Field(
27
- None, description="Additional guidance for agents")
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]]:
@@ -53,12 +61,6 @@ class AIAgent(BaseModel):
53
61
  instructions: str = Field(...,
54
62
  description="Base instructions for the agent")
55
63
  specialization: str = Field(..., description="Agent's specialized domain")
56
- created_at: datetime = Field(
57
- default_factory=datetime.now, description="Creation timestamp")
58
- updated_at: datetime = Field(
59
- default_factory=datetime.now, description="Last update timestamp")
60
- description: Optional[str] = Field(
61
- None, description="Agent description or summary")
62
64
 
63
65
  @field_validator("name", "specialization")
64
66
  @classmethod
@@ -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
- guidance=org_config.get("guidance", "")
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": self._truncate(user_message),
50
- "assistant_message": self._truncate(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)
@@ -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.agent_repository.save_ai_agent(agent)
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
- agent = self.agent_repository.get_ai_agent_by_name(agent_name)
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
- # Add organization goals if available
95
- if self.organization_mission and self.organization_mission.goals:
96
- goals_text = "\n".join(
97
- [f"- {goal}" for goal in self.organization_mission.goals])
98
- system_prompt += f"\n\nORGANIZATION GOALS:\n{goals_text}"
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
- agents = self.agent_repository.get_all_ai_agents()
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
- # Gather from AI agents
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.
149
+ """Execute a tool on behalf of an agent."""
152
150
 
153
- Args:
154
- agent_name: Agent name
155
- tool_name: Tool name
156
- parameters: Tool parameters
157
-
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
- return tool.execute(**parameters)
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.agent_repository.get_ai_agent_by_name(agent_name)
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,54 @@ 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 "tool_call" in data:
270
- # Process tool call with existing method
271
- response_text = await self._handle_tool_call(
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
- )
275
-
276
- # Add to complete text response
277
- complete_text_response += response_text
278
-
279
- # Output response based on format
280
- if output_format == "audio":
281
- async for audio_chunk in self.llm_provider.tts(
282
- text=response_text,
283
- voice=audio_voice,
284
- response_format=audio_output_format
285
- ):
286
- yield audio_chunk
287
- else:
288
- yield response_text
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
+ async for processed_chunk in self.llm_provider.generate_text(
295
+ prompt=process_prompt,
296
+ system_prompt=summary_system_prompt,
297
+ ):
298
+ # Add to complete response
299
+ complete_text_response += processed_chunk
300
+
301
+ # Output response based on format
302
+ if output_format == "audio":
303
+ async for audio_chunk in self.llm_provider.tts(
304
+ text=processed_chunk,
305
+ voice=audio_voice,
306
+ response_format=audio_output_format
307
+ ):
308
+ yield audio_chunk
309
+ else:
310
+ yield processed_chunk
289
311
  else:
290
312
  # For non-tool JSON, still capture the text
291
313
  complete_text_response += json_buffer
@@ -361,30 +383,6 @@ class AgentService(AgentServiceInterface):
361
383
  import traceback
362
384
  print(traceback.format_exc())
363
385
 
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
386
  def _get_tool_usage_prompt(self, agent_name: str) -> str:
389
387
  """Generate JSON-based instructions for tool usage."""
390
388
  # Get tools assigned to this agent
@@ -402,12 +400,12 @@ class AgentService(AgentServiceInterface):
402
400
  tool_example = """
403
401
  For latest news query:
404
402
  {
405
- "tool_call": {
403
+ "tool_calls": [{
406
404
  "name": "search_internet",
407
405
  "parameters": {
408
406
  "query": "latest Solana blockchain news March 2025"
409
407
  }
410
- }
408
+ }]
411
409
  }"""
412
410
 
413
411
  return f"""
@@ -416,12 +414,12 @@ class AgentService(AgentServiceInterface):
416
414
 
417
415
  TOOL USAGE FORMAT:
418
416
  {{
419
- "tool_call": {{
417
+ "tool_calls": [{{
420
418
  "name": "<one_of:{', '.join(available_tool_names)}>",
421
419
  "parameters": {{
422
420
  // parameters as specified in tool definition above
423
421
  }}
424
- }}
422
+ }}]
425
423
  }}
426
424
 
427
425
  {tool_example if tool_example else ''}
@@ -437,3 +435,53 @@ class AgentService(AgentServiceInterface):
437
435
  - No explanation text before or after
438
436
  - Use exact tool names as shown in AVAILABLE TOOLS
439
437
  """
438
+
439
+ async def _handle_multiple_tool_calls(
440
+ self,
441
+ agent_name: str,
442
+ json_chunk: str,
443
+ ) -> AsyncGenerator[str, None]:
444
+ """Handle multiple tool calls concurrently and yield results as they complete."""
445
+ try:
446
+ data = json.loads(json_chunk)
447
+ if "tool_calls" not in data:
448
+ yield json_chunk
449
+ return
450
+
451
+ tool_calls = data["tool_calls"]
452
+
453
+ if not isinstance(tool_calls, list):
454
+ print("Error: tool_calls is not a list")
455
+ yield "Error: 'tool_calls' must be an array of tool calls."
456
+ return
457
+
458
+ # Define individual tool execution coroutine
459
+ async def execute_single_tool(tool_info):
460
+ tool_name = tool_info.get("name")
461
+ parameters = tool_info.get("parameters", {})
462
+ print(f"\nExecuting tool: {tool_name}")
463
+ print(f"With parameters: {parameters}")
464
+
465
+ if not tool_name:
466
+ return f"Error: Missing tool name in tool call."
467
+
468
+ result = self.execute_tool(agent_name, tool_name, parameters)
469
+
470
+ if result.get("status") == "success":
471
+ return f"Result from {tool_name}: {result.get('result', '')}"
472
+ else:
473
+ return f"Error from {tool_name}: {result.get('message', 'Unknown error')}"
474
+
475
+ # Execute all tool calls concurrently
476
+ tasks = [execute_single_tool(tool_call)
477
+ for tool_call in tool_calls]
478
+ for task in asyncio.as_completed(tasks):
479
+ result = await task
480
+ yield result
481
+
482
+ except json.JSONDecodeError:
483
+ yield "Error: Could not parse tool calls JSON."
484
+ except Exception as e:
485
+ import traceback
486
+ print(traceback.format_exc())
487
+ yield f"Error processing tool calls: {str(e)}"
@@ -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": truncated_user_message},
279
- {"role": "assistant", "content": truncated_assistant_message},
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 + "..."
@@ -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
- 1. The primary specialization needed to address it
43
- 2. Any secondary specializations that might be helpful
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
- User Query: {query}
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="Analyze user queries to determine appropriate routing.",
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
- if len(self.agent_service.get_all_ai_agents()) == 1:
88
- return next(iter(self.agent_service.get_all_ai_agents().keys()))
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 needed
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
- # Create a list to score agents
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 primary specialization
129
- if agent.specialization.lower() == primary_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
- if hasattr(agent, 'secondary_specializations'):
134
- for sec_spec in secondary_specializations:
135
- if sec_spec.lower() in [s.lower() for s in agent.secondary_specializations]:
136
- score += 3
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 good match, return the first AI agent as fallback
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.7
3
+ Version: 17.1.9
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
- "guidance": "Align all interactions with the success of the user while respecting human dignity."
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=Ak_hD5gTCzRqAHLmqtxnny0Xki1qAKR7RzLW9LOQBTg,2930
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=7gMfX-80Olp11ZSzPsihH_whjlqbEEmfQD26253EbMk,5699
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/agent.py,sha256=e1rnsQiigkKwJNLKro86a3b6TBiky3GMfmCRc5b_jPw,3187
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=RLsr5b-HbAsjhXxJThPnnTNz6OWPQbnDF4LNl6BGyVg,16740
32
- solana_agent/services/query.py,sha256=rm7XlTCcp5NeorIaLUdr7rdxtWCgg1Q1qe5YuI1bumo,11246
33
- solana_agent/services/routing.py,sha256=TPJ2Pas4acE93QzMEV6ZP670OtTNrVEPa76fz6urEV4,4996
34
- solana_agent-17.1.7.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
35
- solana_agent-17.1.7.dist-info/METADATA,sha256=sqs6N7pQNy-3Tnmk0_wD5kxHZZf3GtU5C5seXSmoX7M,4720
36
- solana_agent-17.1.7.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
37
- solana_agent-17.1.7.dist-info/RECORD,,
29
+ solana_agent/services/agent.py,sha256=K3_DB02VRjPKa4z-ik-1sz9fYGBWGwLjFvQugJKxonw,19295
30
+ solana_agent/services/query.py,sha256=qXrvzAyMqESdF8QD3xYaz2vyfR7ndLpsh2TahYQ-LYg,10414
31
+ solana_agent/services/routing.py,sha256=IPvBicgTYXqQ8iIRaatCsBGQVsOBGdAkq2i6U8hZlOY,6479
32
+ solana_agent-17.1.9.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
33
+ solana_agent-17.1.9.dist-info/METADATA,sha256=Svsmh7LG38ajUZRlTRlyaelmdXiF_LAQT1BslL0ESrM,4692
34
+ solana_agent-17.1.9.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
35
+ solana_agent-17.1.9.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})