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.
- solana_agent/__init__.py +10 -5
- solana_agent/adapters/ffmpeg_transcoder.py +375 -0
- solana_agent/adapters/mongodb_adapter.py +15 -2
- solana_agent/adapters/openai_adapter.py +679 -0
- solana_agent/adapters/openai_realtime_ws.py +1813 -0
- solana_agent/adapters/pinecone_adapter.py +543 -0
- solana_agent/cli.py +128 -0
- solana_agent/client/solana_agent.py +180 -20
- solana_agent/domains/agent.py +13 -13
- solana_agent/domains/routing.py +18 -8
- solana_agent/factories/agent_factory.py +239 -38
- solana_agent/guardrails/pii.py +107 -0
- solana_agent/interfaces/client/client.py +95 -12
- solana_agent/interfaces/guardrails/guardrails.py +26 -0
- solana_agent/interfaces/plugins/plugins.py +2 -1
- solana_agent/interfaces/providers/__init__.py +0 -0
- solana_agent/interfaces/providers/audio.py +40 -0
- solana_agent/interfaces/providers/data_storage.py +9 -2
- solana_agent/interfaces/providers/llm.py +86 -9
- solana_agent/interfaces/providers/memory.py +13 -1
- solana_agent/interfaces/providers/realtime.py +212 -0
- solana_agent/interfaces/providers/vector_storage.py +53 -0
- solana_agent/interfaces/services/agent.py +27 -12
- solana_agent/interfaces/services/knowledge_base.py +59 -0
- solana_agent/interfaces/services/query.py +41 -8
- solana_agent/interfaces/services/routing.py +0 -1
- solana_agent/plugins/manager.py +37 -16
- solana_agent/plugins/registry.py +34 -19
- solana_agent/plugins/tools/__init__.py +0 -5
- solana_agent/plugins/tools/auto_tool.py +1 -0
- solana_agent/repositories/memory.py +332 -111
- solana_agent/services/__init__.py +1 -1
- solana_agent/services/agent.py +390 -241
- solana_agent/services/knowledge_base.py +768 -0
- solana_agent/services/query.py +1858 -153
- solana_agent/services/realtime.py +626 -0
- solana_agent/services/routing.py +104 -51
- solana_agent-31.4.0.dist-info/METADATA +1070 -0
- solana_agent-31.4.0.dist-info/RECORD +49 -0
- {solana_agent-20.1.2.dist-info → solana_agent-31.4.0.dist-info}/WHEEL +1 -1
- solana_agent-31.4.0.dist-info/entry_points.txt +3 -0
- solana_agent/adapters/llm_adapter.py +0 -160
- solana_agent-20.1.2.dist-info/METADATA +0 -464
- solana_agent-20.1.2.dist-info/RECORD +0 -35
- {solana_agent-20.1.2.dist-info → solana_agent-31.4.0.dist-info/licenses}/LICENSE +0 -0
solana_agent/services/routing.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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="
|
|
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.
|
|
82
|
-
"secondary_specializations": analysis.
|
|
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
|
-
|
|
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":
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
|
156
|
-
|
|
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
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|