solana-agent 31.2.3__tar.gz → 31.2.5__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-31.2.3 → solana_agent-31.2.5}/PKG-INFO +7 -7
- {solana_agent-31.2.3 → solana_agent-31.2.5}/README.md +3 -3
- {solana_agent-31.2.3 → solana_agent-31.2.5}/pyproject.toml +6 -6
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/ffmpeg_transcoder.py +40 -4
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/openai_realtime_ws.py +9 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/domains/routing.py +7 -3
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/routing.py +24 -8
- {solana_agent-31.2.3 → solana_agent-31.2.5}/LICENSE +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/mongodb_adapter.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/openai_adapter.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/pinecone_adapter.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/cli.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/client/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/client/solana_agent.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/domains/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/domains/agent.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/factories/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/factories/agent_factory.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/guardrails/pii.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/client/client.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/guardrails/guardrails.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/plugins/plugins.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/audio.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/data_storage.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/llm.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/memory.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/realtime.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/vector_storage.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/agent.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/knowledge_base.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/query.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/routing.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/manager.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/registry.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/tools/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/tools/auto_tool.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/repositories/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/repositories/memory.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/__init__.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/agent.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/knowledge_base.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/query.py +0 -0
- {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/realtime.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: solana-agent
|
3
|
-
Version: 31.2.
|
3
|
+
Version: 31.2.5
|
4
4
|
Summary: AI Agents for Solana
|
5
5
|
License: MIT
|
6
6
|
Keywords: solana,solana ai,solana agent,ai,ai agent,ai agents
|
@@ -17,12 +17,12 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
17
|
Requires-Dist: instructor (==1.11.3)
|
18
18
|
Requires-Dist: llama-index-core (==0.14.0)
|
19
19
|
Requires-Dist: llama-index-embeddings-openai (==0.5.1)
|
20
|
-
Requires-Dist: logfire (==4.
|
21
|
-
Requires-Dist: openai (==1.107.
|
20
|
+
Requires-Dist: logfire (==4.7.0)
|
21
|
+
Requires-Dist: openai (==1.107.2)
|
22
22
|
Requires-Dist: pillow (==11.3.0)
|
23
23
|
Requires-Dist: pinecone[asyncio] (==7.3.0)
|
24
24
|
Requires-Dist: pydantic (>=2)
|
25
|
-
Requires-Dist: pymongo (==4.
|
25
|
+
Requires-Dist: pymongo (==4.15.0)
|
26
26
|
Requires-Dist: pypdf (==6.0.0)
|
27
27
|
Requires-Dist: rich (>=13,<14.0)
|
28
28
|
Requires-Dist: scrubadub (==2.0.1)
|
@@ -335,18 +335,18 @@ async def generate():
|
|
335
335
|
rt_encode_output=True,
|
336
336
|
rt_voice="marin",
|
337
337
|
output_format="audio",
|
338
|
-
audio_output_format="
|
338
|
+
audio_output_format="mp3",
|
339
339
|
audio_input_format="mp4",
|
340
340
|
):
|
341
341
|
yield chunk
|
342
342
|
|
343
343
|
return StreamingResponse(
|
344
344
|
content=generate(),
|
345
|
-
media_type="audio/
|
345
|
+
media_type="audio/mp3",
|
346
346
|
headers={
|
347
347
|
"Cache-Control": "no-store",
|
348
348
|
"Pragma": "no-cache",
|
349
|
-
"Content-Disposition": "inline; filename=stream.
|
349
|
+
"Content-Disposition": "inline; filename=stream.mp3",
|
350
350
|
"X-Accel-Buffering": "no",
|
351
351
|
},
|
352
352
|
)
|
@@ -299,18 +299,18 @@ async def generate():
|
|
299
299
|
rt_encode_output=True,
|
300
300
|
rt_voice="marin",
|
301
301
|
output_format="audio",
|
302
|
-
audio_output_format="
|
302
|
+
audio_output_format="mp3",
|
303
303
|
audio_input_format="mp4",
|
304
304
|
):
|
305
305
|
yield chunk
|
306
306
|
|
307
307
|
return StreamingResponse(
|
308
308
|
content=generate(),
|
309
|
-
media_type="audio/
|
309
|
+
media_type="audio/mp3",
|
310
310
|
headers={
|
311
311
|
"Cache-Control": "no-store",
|
312
312
|
"Pragma": "no-cache",
|
313
|
-
"Content-Disposition": "inline; filename=stream.
|
313
|
+
"Content-Disposition": "inline; filename=stream.mp3",
|
314
314
|
"X-Accel-Buffering": "no",
|
315
315
|
},
|
316
316
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "solana-agent"
|
3
|
-
version = "31.2.
|
3
|
+
version = "31.2.5"
|
4
4
|
description = "AI Agents for Solana"
|
5
5
|
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
|
6
6
|
license = "MIT"
|
@@ -24,9 +24,9 @@ testpaths = ["tests"]
|
|
24
24
|
|
25
25
|
[tool.poetry.dependencies]
|
26
26
|
python = ">=3.12,<4.0"
|
27
|
-
openai = "1.107.
|
27
|
+
openai = "1.107.2"
|
28
28
|
pydantic = ">=2"
|
29
|
-
pymongo = "4.
|
29
|
+
pymongo = "4.15.0"
|
30
30
|
zep-cloud = "3.4.3"
|
31
31
|
instructor = "1.11.3"
|
32
32
|
pinecone = { version = "7.3.0", extras = ["asyncio"] }
|
@@ -34,7 +34,7 @@ llama-index-core = "0.14.0"
|
|
34
34
|
llama-index-embeddings-openai = "0.5.1"
|
35
35
|
pypdf = "6.0.0"
|
36
36
|
scrubadub = "2.0.1"
|
37
|
-
logfire = "4.
|
37
|
+
logfire = "4.7.0"
|
38
38
|
typer = "0.17.4"
|
39
39
|
rich = ">=13,<14.0"
|
40
40
|
pillow = "11.3.0"
|
@@ -43,7 +43,7 @@ websockets = ">=13,<16"
|
|
43
43
|
[tool.poetry.group.dev.dependencies]
|
44
44
|
pytest = "^8.4.2"
|
45
45
|
pytest-cov = "^7.0.0"
|
46
|
-
pytest-asyncio = "^1.
|
46
|
+
pytest-asyncio = "^1.2.0"
|
47
47
|
pytest-mock = "^3.15.0"
|
48
48
|
pytest-github-actions-annotate-failures = "^0.3.0"
|
49
49
|
sphinx = "^8.2.3"
|
@@ -51,7 +51,7 @@ sphinx-rtd-theme = "^3.0.2"
|
|
51
51
|
myst-parser = "^4.0.1"
|
52
52
|
sphinx-autobuild = "^2025.08.25"
|
53
53
|
mongomock = "^4.3.0"
|
54
|
-
ruff = "^0.
|
54
|
+
ruff = "^0.13.0"
|
55
55
|
|
56
56
|
[tool.poetry.scripts]
|
57
57
|
solana-agent = "solana_agent.cli:app"
|
@@ -4,6 +4,8 @@ import asyncio
|
|
4
4
|
import contextlib
|
5
5
|
import logging
|
6
6
|
from typing import List, AsyncGenerator
|
7
|
+
import tempfile
|
8
|
+
import os
|
7
9
|
|
8
10
|
from solana_agent.interfaces.providers.audio import AudioTranscoder
|
9
11
|
|
@@ -49,11 +51,45 @@ class FFmpegTranscoder(AudioTranscoder):
|
|
49
51
|
rate_hz,
|
50
52
|
len(audio_bytes),
|
51
53
|
)
|
52
|
-
#
|
53
|
-
|
54
|
+
# iOS-recorded MP4/M4A often requires a seekable input for reliable demuxing.
|
55
|
+
# Decode from a temporary file instead of stdin for MP4/M4A.
|
54
56
|
if input_mime in ("audio/mp4", "audio/m4a"):
|
55
|
-
|
56
|
-
|
57
|
+
suffix = ".m4a" if input_mime == "audio/m4a" else ".mp4"
|
58
|
+
tmp_path = None
|
59
|
+
try:
|
60
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tf:
|
61
|
+
tmp_path = tf.name
|
62
|
+
tf.write(audio_bytes)
|
63
|
+
args = [
|
64
|
+
"-hide_banner",
|
65
|
+
"-loglevel",
|
66
|
+
"error",
|
67
|
+
"-i",
|
68
|
+
tmp_path,
|
69
|
+
"-vn", # ignore any video tracks
|
70
|
+
"-acodec",
|
71
|
+
"pcm_s16le",
|
72
|
+
"-ac",
|
73
|
+
"1",
|
74
|
+
"-ar",
|
75
|
+
str(rate_hz),
|
76
|
+
"-f",
|
77
|
+
"s16le",
|
78
|
+
"pipe:1",
|
79
|
+
]
|
80
|
+
out = await self._run_ffmpeg(args, b"")
|
81
|
+
logger.info(
|
82
|
+
"Transcoded (MP4/M4A temp-file) to PCM16: output_len=%d", len(out)
|
83
|
+
)
|
84
|
+
return out
|
85
|
+
finally:
|
86
|
+
if tmp_path:
|
87
|
+
with contextlib.suppress(Exception):
|
88
|
+
os.remove(tmp_path)
|
89
|
+
|
90
|
+
# For other formats, prefer a format hint when helpful and decode from stdin.
|
91
|
+
hinted_format = None
|
92
|
+
if input_mime in ("audio/aac",):
|
57
93
|
# Raw AAC is typically in ADTS stream format
|
58
94
|
hinted_format = "adts"
|
59
95
|
elif input_mime in ("audio/ogg", "audio/webm"):
|
@@ -1037,6 +1037,15 @@ class OpenAIRealtimeWebSocketSession(BaseRealtimeSession):
|
|
1037
1037
|
if "tools" in patch:
|
1038
1038
|
patch["tools"] = _strip_tool_strict(patch["tools"]) # idempotent
|
1039
1039
|
|
1040
|
+
# Per server requirements, always include session.type and output_modalities
|
1041
|
+
try:
|
1042
|
+
patch["type"] = "realtime"
|
1043
|
+
# Preserve caller-provided output_modalities if present, otherwise default to audio
|
1044
|
+
if "output_modalities" not in patch:
|
1045
|
+
patch["output_modalities"] = ["audio"]
|
1046
|
+
except Exception:
|
1047
|
+
pass
|
1048
|
+
|
1040
1049
|
payload = {"type": "session.update", "session": patch}
|
1041
1050
|
# Mark awaiting updated and store last patch
|
1042
1051
|
self._last_session_patch = patch or {}
|
@@ -5,9 +5,13 @@ from pydantic import BaseModel, Field
|
|
5
5
|
class QueryAnalysis(BaseModel):
|
6
6
|
"""Analysis of a user query for routing purposes."""
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
primary_agent: str = Field(
|
9
|
+
...,
|
10
|
+
description="Name of the primary agent that should handle this query (must be one of the available agent names)",
|
11
|
+
)
|
12
|
+
secondary_agents: List[str] = Field(
|
13
|
+
...,
|
14
|
+
description="Names of secondary agents that might be helpful (must be from the available agent names)",
|
11
15
|
)
|
12
16
|
complexity_level: int = Field(..., description="Complexity level (1-5)")
|
13
17
|
topics: List[str] = Field(..., description="Key topics in the query")
|
@@ -81,26 +81,34 @@ class RoutingService(RoutingServiceInterface):
|
|
81
81
|
|
82
82
|
USER QUERY: {query}
|
83
83
|
|
84
|
+
INSTRUCTIONS:
|
85
|
+
- Look at the user query and match it to the most appropriate agent from the list above
|
86
|
+
- If the user mentions a specific topic or need that matches an agent's specialization, choose that agent
|
87
|
+
- Return the EXACT agent name (not the specialization description)
|
88
|
+
|
84
89
|
Please determine:
|
85
|
-
1.
|
86
|
-
2.
|
87
|
-
3.
|
88
|
-
4.
|
90
|
+
1. primary_agent: The exact name of the best matching agent (e.g., "onboarding", "support")
|
91
|
+
2. secondary_agents: Names of other agents that might help (empty list if none)
|
92
|
+
3. complexity_level: 1-5 (5 being most complex)
|
93
|
+
4. topics: Key topics mentioned
|
94
|
+
5. confidence: 0.0-1.0 (how confident you are in this routing decision)
|
89
95
|
"""
|
90
96
|
|
91
97
|
try:
|
92
98
|
analysis = await self.llm_provider.parse_structured_output(
|
93
99
|
prompt=prompt,
|
94
|
-
system_prompt="
|
100
|
+
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.",
|
95
101
|
model_class=QueryAnalysis,
|
96
102
|
api_key=self.api_key,
|
97
103
|
base_url=self.base_url,
|
98
104
|
model=self.model,
|
99
105
|
)
|
100
106
|
|
107
|
+
logger.debug(f"LLM analysis result: {analysis}")
|
108
|
+
|
101
109
|
return {
|
102
|
-
"primary_specialization": analysis.
|
103
|
-
"secondary_specializations": analysis.
|
110
|
+
"primary_specialization": analysis.primary_agent,
|
111
|
+
"secondary_specializations": analysis.secondary_agents,
|
104
112
|
"complexity_level": analysis.complexity_level,
|
105
113
|
"topics": analysis.topics,
|
106
114
|
"confidence": analysis.confidence,
|
@@ -143,9 +151,11 @@ class RoutingService(RoutingServiceInterface):
|
|
143
151
|
|
144
152
|
# Always analyze with a small model to select the best agent
|
145
153
|
analysis = await self._analyze_query(query)
|
154
|
+
logger.debug(f"Routing analysis for query '{query}': {analysis}")
|
146
155
|
best_agent = await self._find_best_ai_agent(
|
147
156
|
analysis["primary_specialization"], analysis["secondary_specializations"]
|
148
157
|
)
|
158
|
+
logger.debug(f"Selected agent: {best_agent}")
|
149
159
|
chosen = best_agent or next(iter(agents.keys()))
|
150
160
|
self._last_agent = chosen
|
151
161
|
return chosen
|
@@ -171,6 +181,7 @@ class RoutingService(RoutingServiceInterface):
|
|
171
181
|
|
172
182
|
# First, check if primary_specialization is directly an agent name
|
173
183
|
if primary_specialization in ai_agents:
|
184
|
+
logger.debug(f"Direct agent match: {primary_specialization}")
|
174
185
|
return primary_specialization
|
175
186
|
|
176
187
|
# If not a direct agent name match, use specialization matching
|
@@ -185,6 +196,9 @@ class RoutingService(RoutingServiceInterface):
|
|
185
196
|
or primary_specialization.lower() in agent.specialization.lower()
|
186
197
|
):
|
187
198
|
score += 10
|
199
|
+
logger.debug(
|
200
|
+
f"Specialization match for {agent_id}: '{agent.specialization}' matches '{primary_specialization}'"
|
201
|
+
)
|
188
202
|
|
189
203
|
# Check secondary specializations
|
190
204
|
for sec_spec in secondary_specializations:
|
@@ -209,6 +223,8 @@ class RoutingService(RoutingServiceInterface):
|
|
209
223
|
|
210
224
|
# If no match found, return first agent as fallback
|
211
225
|
if ai_agents:
|
212
|
-
|
226
|
+
fallback_agent = next(iter(ai_agents.keys()))
|
227
|
+
logger.debug(f"No match found, using fallback agent: {fallback_agent}")
|
228
|
+
return fallback_agent
|
213
229
|
|
214
230
|
return None
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/guardrails/guardrails.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/data_storage.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/vector_storage.py
RENAMED
File without changes
|
File without changes
|
{solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/knowledge_base.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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|