solana-agent 20.1.2__py3-none-any.whl → 31.4.0__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.
Files changed (45) hide show
  1. solana_agent/__init__.py +10 -5
  2. solana_agent/adapters/ffmpeg_transcoder.py +375 -0
  3. solana_agent/adapters/mongodb_adapter.py +15 -2
  4. solana_agent/adapters/openai_adapter.py +679 -0
  5. solana_agent/adapters/openai_realtime_ws.py +1813 -0
  6. solana_agent/adapters/pinecone_adapter.py +543 -0
  7. solana_agent/cli.py +128 -0
  8. solana_agent/client/solana_agent.py +180 -20
  9. solana_agent/domains/agent.py +13 -13
  10. solana_agent/domains/routing.py +18 -8
  11. solana_agent/factories/agent_factory.py +239 -38
  12. solana_agent/guardrails/pii.py +107 -0
  13. solana_agent/interfaces/client/client.py +95 -12
  14. solana_agent/interfaces/guardrails/guardrails.py +26 -0
  15. solana_agent/interfaces/plugins/plugins.py +2 -1
  16. solana_agent/interfaces/providers/__init__.py +0 -0
  17. solana_agent/interfaces/providers/audio.py +40 -0
  18. solana_agent/interfaces/providers/data_storage.py +9 -2
  19. solana_agent/interfaces/providers/llm.py +86 -9
  20. solana_agent/interfaces/providers/memory.py +13 -1
  21. solana_agent/interfaces/providers/realtime.py +212 -0
  22. solana_agent/interfaces/providers/vector_storage.py +53 -0
  23. solana_agent/interfaces/services/agent.py +27 -12
  24. solana_agent/interfaces/services/knowledge_base.py +59 -0
  25. solana_agent/interfaces/services/query.py +41 -8
  26. solana_agent/interfaces/services/routing.py +0 -1
  27. solana_agent/plugins/manager.py +37 -16
  28. solana_agent/plugins/registry.py +34 -19
  29. solana_agent/plugins/tools/__init__.py +0 -5
  30. solana_agent/plugins/tools/auto_tool.py +1 -0
  31. solana_agent/repositories/memory.py +332 -111
  32. solana_agent/services/__init__.py +1 -1
  33. solana_agent/services/agent.py +390 -241
  34. solana_agent/services/knowledge_base.py +768 -0
  35. solana_agent/services/query.py +1858 -153
  36. solana_agent/services/realtime.py +626 -0
  37. solana_agent/services/routing.py +104 -51
  38. solana_agent-31.4.0.dist-info/METADATA +1070 -0
  39. solana_agent-31.4.0.dist-info/RECORD +49 -0
  40. {solana_agent-20.1.2.dist-info → solana_agent-31.4.0.dist-info}/WHEEL +1 -1
  41. solana_agent-31.4.0.dist-info/entry_points.txt +3 -0
  42. solana_agent/adapters/llm_adapter.py +0 -160
  43. solana_agent-20.1.2.dist-info/METADATA +0 -464
  44. solana_agent-20.1.2.dist-info/RECORD +0 -35
  45. {solana_agent-20.1.2.dist-info → solana_agent-31.4.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,15 +1,15 @@
1
- """
2
- Routing service implementation.
3
-
4
- This service manages query routing to appropriate agents based on
5
- specializations and query analysis.
6
- """
1
+ import logging
7
2
  from typing import Dict, List, Optional, Any
8
- from solana_agent.interfaces.services.routing import RoutingService as RoutingServiceInterface
3
+ from solana_agent.interfaces.services.routing import (
4
+ RoutingService as RoutingServiceInterface,
5
+ )
9
6
  from solana_agent.interfaces.services.agent import AgentService
10
7
  from solana_agent.interfaces.providers.llm import LLMProvider
11
8
  from solana_agent.domains.routing import QueryAnalysis
12
9
 
10
+ # Setup logger for this module
11
+ logger = logging.getLogger(__name__)
12
+
13
13
 
14
14
  class RoutingService(RoutingServiceInterface):
15
15
  """Service for routing queries to appropriate agents."""
@@ -18,15 +18,34 @@ class RoutingService(RoutingServiceInterface):
18
18
  self,
19
19
  llm_provider: LLMProvider,
20
20
  agent_service: AgentService,
21
- ):
21
+ api_key: Optional[str] = None,
22
+ base_url: Optional[str] = None,
23
+ model: Optional[str] = None,
24
+ ) -> None:
22
25
  """Initialize the routing service.
23
26
 
24
27
  Args:
25
28
  llm_provider: Provider for language model interactions
26
29
  agent_service: Service for agent management
30
+ api_key: Optional API key for custom LLM provider
31
+ base_url: Optional base URL for custom LLM provider (e.g., Grok)
32
+ model: Optional model name to use for routing
27
33
  """
28
34
  self.llm_provider = llm_provider
29
35
  self.agent_service = agent_service
36
+ self.api_key = api_key
37
+ self.base_url = base_url
38
+ # Use provided model, or default based on whether using custom provider
39
+ if model:
40
+ self.model = model
41
+ elif base_url:
42
+ # Using custom provider (e.g., Grok) but no model specified - use provider's default
43
+ self.model = None # Will use adapter's default
44
+ else:
45
+ # Using OpenAI - default to small, cheap model for routing
46
+ self.model = "gpt-4.1-mini"
47
+ # Simple sticky session: remember last routed agent in-process
48
+ self._last_agent = None
30
49
 
31
50
  async def _analyze_query(self, query: str) -> Dict[str, Any]:
32
51
  """Analyze a query to determine routing information.
@@ -42,57 +61,74 @@ class RoutingService(RoutingServiceInterface):
42
61
  available_specializations = []
43
62
 
44
63
  for agent_id, agent in agents.items():
45
- available_specializations.append({
46
- "agent_name": agent_id,
47
- "specialization": agent.specialization,
48
- })
64
+ available_specializations.append(
65
+ {
66
+ "agent_name": agent_id,
67
+ "specialization": agent.specialization,
68
+ }
69
+ )
49
70
 
50
- specializations_text = "\n".join([
51
- f"- {spec['agent_name']}: {spec['specialization']}"
52
- for spec in available_specializations
53
- ])
71
+ specializations_text = "\n".join(
72
+ [
73
+ f"- {spec['agent_name']}: {spec['specialization']}"
74
+ for spec in available_specializations
75
+ ]
76
+ )
54
77
 
55
78
  prompt = f"""
56
79
  Analyze this user query and determine which agent would be best suited to answer it.
57
-
80
+
58
81
  AVAILABLE AGENTS AND THEIR SPECIALIZATIONS:
59
82
  {specializations_text}
60
-
83
+
61
84
  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)
66
- 3. The complexity level (1-5, where 5 is most complex)
67
- 4. Any key topics or technologies mentioned
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.
85
+
86
+ ROUTING RULES:
87
+ - Match the user query to the agent whose specialization best fits the user's intent
88
+ - Return the EXACT agent name that matches best
89
+
90
+ INSTRUCTIONS:
91
+ - primary_agent: The exact name of the best matching agent (e.g., "onboarding", "event_feedback")
92
+ - secondary_agents: Other agents that might help (usually empty)
93
+ - complexity_level: 1-5 (5 being most complex)
94
+ - topics: Key topics mentioned
95
+ - confidence: 0.0-1.0 (how confident you are in this routing decision)
96
+
97
+ For the query "{query}", which agent should handle it?
71
98
  """
72
99
 
73
100
  try:
74
101
  analysis = await self.llm_provider.parse_structured_output(
75
102
  prompt=prompt,
76
- system_prompt="Match user queries to the most appropriate agent based on specializations.",
103
+ system_prompt="You are an expert at routing user queries to the most appropriate AI agent. Always return the exact agent name that best matches the user's needs based on the specializations provided. If the user mentions a specific topic, prioritize agents whose specialization matches that topic.",
77
104
  model_class=QueryAnalysis,
105
+ api_key=self.api_key,
106
+ base_url=self.base_url,
107
+ model=self.model,
78
108
  )
79
109
 
110
+ logger.debug(f"LLM analysis result: {analysis}")
111
+
80
112
  return {
81
- "primary_specialization": analysis.primary_specialization,
82
- "secondary_specializations": analysis.secondary_specializations,
113
+ "primary_specialization": analysis.primary_agent,
114
+ "secondary_specializations": analysis.secondary_agents,
83
115
  "complexity_level": analysis.complexity_level,
84
116
  "topics": analysis.topics,
85
- "confidence": analysis.confidence
117
+ "confidence": analysis.confidence,
86
118
  }
87
119
  except Exception as e:
88
- print(f"Error analyzing query: {e}")
120
+ logger.error(f"Error analyzing query: {e}")
121
+ logger.debug(f"Query that failed: {query}")
122
+ logger.debug(f"Available agents: {list(agents.keys())}")
89
123
  # Return default analysis on error
124
+ first_agent = list(agents.keys())[0] if agents else "general"
125
+ logger.debug(f"Defaulting to first agent: {first_agent}")
90
126
  return {
91
- "primary_specialization": list(agents.keys())[0] if agents else "general",
127
+ "primary_specialization": first_agent,
92
128
  "secondary_specializations": [],
93
129
  "complexity_level": 1,
94
130
  "topics": [],
95
- "confidence": 0.0
131
+ "confidence": 0.0,
96
132
  }
97
133
 
98
134
  async def route_query(self, query: str) -> str: # pragma: no cover
@@ -107,20 +143,27 @@ class RoutingService(RoutingServiceInterface):
107
143
  # If only one agent - use that agent
108
144
  agents = self.agent_service.get_all_ai_agents()
109
145
  if len(agents) == 1:
110
- print(f"Only one agent available: {next(iter(agents.keys()))}")
111
- return next(iter(agents.keys()))
112
-
113
- # Analyze query
146
+ agent_name = next(iter(agents.keys()))
147
+ logger.info(f"Only one agent available: {agent_name}") # Use logger.info
148
+ self._last_agent = agent_name
149
+ return agent_name
150
+
151
+ # Short reply bypass and default stickiness
152
+ short = query.strip().lower()
153
+ short_replies = {"", "yes", "no", "ok", "k", "y", "n", "1", "0"}
154
+ if short in short_replies and self._last_agent:
155
+ return self._last_agent
156
+
157
+ # Always analyze with a small model to select the best agent
114
158
  analysis = await self._analyze_query(query)
115
-
116
- # Find best agent based on analysis
159
+ logger.debug(f"Routing analysis for query '{query}': {analysis}")
117
160
  best_agent = await self._find_best_ai_agent(
118
- analysis["primary_specialization"],
119
- analysis["secondary_specializations"]
161
+ analysis["primary_specialization"], analysis["secondary_specializations"]
120
162
  )
121
-
122
- # Return best agent
123
- return best_agent
163
+ logger.debug(f"Selected agent: {best_agent}")
164
+ chosen = best_agent or next(iter(agents.keys()))
165
+ self._last_agent = chosen
166
+ return chosen
124
167
 
125
168
  async def _find_best_ai_agent(
126
169
  self,
@@ -143,6 +186,7 @@ class RoutingService(RoutingServiceInterface):
143
186
 
144
187
  # First, check if primary_specialization is directly an agent name
145
188
  if primary_specialization in ai_agents:
189
+ logger.debug(f"Direct agent match: {primary_specialization}")
146
190
  return primary_specialization
147
191
 
148
192
  # If not a direct agent name match, use specialization matching
@@ -152,24 +196,31 @@ class RoutingService(RoutingServiceInterface):
152
196
  score = 0
153
197
 
154
198
  # Check for specialization match
155
- if agent.specialization.lower() in primary_specialization.lower() or \
156
- primary_specialization.lower() in agent.specialization.lower():
199
+ if (
200
+ agent.specialization.lower() in primary_specialization.lower()
201
+ or primary_specialization.lower() in agent.specialization.lower()
202
+ ):
157
203
  score += 10
204
+ logger.debug(
205
+ f"Specialization match for {agent_id}: '{agent.specialization}' matches '{primary_specialization}'"
206
+ )
158
207
 
159
208
  # Check secondary specializations
160
209
  for sec_spec in secondary_specializations:
161
210
  if sec_spec in ai_agents: # Direct agent name match
162
211
  if sec_spec == agent_id:
163
212
  score += 5
164
- elif agent.specialization.lower() in sec_spec.lower() or \
165
- sec_spec.lower() in agent.specialization.lower():
213
+ elif (
214
+ agent.specialization.lower() in sec_spec.lower()
215
+ or sec_spec.lower() in agent.specialization.lower()
216
+ ):
166
217
  score += 3
167
218
 
168
219
  agent_scores.append((agent_id, score))
169
220
 
170
221
  # Sort by score
171
222
  agent_scores.sort(key=lambda x: x[1], reverse=True)
172
- print(f"Agent scores: {agent_scores}")
223
+ logger.debug(f"Agent scores: {agent_scores}") # Use logger.debug
173
224
 
174
225
  # Return the highest scoring agent, if any
175
226
  if agent_scores and agent_scores[0][1] > 0:
@@ -177,6 +228,8 @@ class RoutingService(RoutingServiceInterface):
177
228
 
178
229
  # If no match found, return first agent as fallback
179
230
  if ai_agents:
180
- return next(iter(ai_agents.keys()))
231
+ fallback_agent = next(iter(ai_agents.keys()))
232
+ logger.debug(f"No match found, using fallback agent: {fallback_agent}")
233
+ return fallback_agent
181
234
 
182
235
  return None