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.
Files changed (47) hide show
  1. {solana_agent-31.2.3 → solana_agent-31.2.5}/PKG-INFO +7 -7
  2. {solana_agent-31.2.3 → solana_agent-31.2.5}/README.md +3 -3
  3. {solana_agent-31.2.3 → solana_agent-31.2.5}/pyproject.toml +6 -6
  4. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/ffmpeg_transcoder.py +40 -4
  5. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/openai_realtime_ws.py +9 -0
  6. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/domains/routing.py +7 -3
  7. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/routing.py +24 -8
  8. {solana_agent-31.2.3 → solana_agent-31.2.5}/LICENSE +0 -0
  9. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/__init__.py +0 -0
  10. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/__init__.py +0 -0
  11. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/mongodb_adapter.py +0 -0
  12. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/openai_adapter.py +0 -0
  13. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/adapters/pinecone_adapter.py +0 -0
  14. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/cli.py +0 -0
  15. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/client/__init__.py +0 -0
  16. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/client/solana_agent.py +0 -0
  17. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/domains/__init__.py +0 -0
  18. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/domains/agent.py +0 -0
  19. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/factories/__init__.py +0 -0
  20. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/factories/agent_factory.py +0 -0
  21. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/guardrails/pii.py +0 -0
  22. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/__init__.py +0 -0
  23. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/client/client.py +0 -0
  24. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/guardrails/guardrails.py +0 -0
  25. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/plugins/plugins.py +0 -0
  26. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/audio.py +0 -0
  27. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/data_storage.py +0 -0
  28. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/llm.py +0 -0
  29. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/memory.py +0 -0
  30. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/realtime.py +0 -0
  31. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/providers/vector_storage.py +0 -0
  32. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/agent.py +0 -0
  33. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/knowledge_base.py +0 -0
  34. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/query.py +0 -0
  35. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/interfaces/services/routing.py +0 -0
  36. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/__init__.py +0 -0
  37. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/manager.py +0 -0
  38. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/registry.py +0 -0
  39. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/tools/__init__.py +0 -0
  40. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/plugins/tools/auto_tool.py +0 -0
  41. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/repositories/__init__.py +0 -0
  42. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/repositories/memory.py +0 -0
  43. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/__init__.py +0 -0
  44. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/agent.py +0 -0
  45. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/knowledge_base.py +0 -0
  46. {solana_agent-31.2.3 → solana_agent-31.2.5}/solana_agent/services/query.py +0 -0
  47. {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
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.5.0)
21
- Requires-Dist: openai (==1.107.0)
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.14.1)
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="m4a",
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/mp4",
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.m4a",
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="m4a",
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/mp4",
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.m4a",
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"
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.0"
27
+ openai = "1.107.2"
28
28
  pydantic = ">=2"
29
- pymongo = "4.14.1"
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.5.0"
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.1.0"
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.12.12"
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
- # Prefer to hint format for common containers/codecs; ffmpeg can still autodetect if hint is wrong.
53
- hinted_format = None
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
- hinted_format = "mp4"
56
- elif input_mime in ("audio/aac",):
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
- primary_specialization: str = Field(..., description="Main specialization needed")
9
- secondary_specializations: List[str] = Field(
10
- ..., description="Other helpful specializations"
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. Which agent is the primary best match for this query (must be one of the listed agents)
86
- 2. Any secondary agents that might be helpful (must be from the listed agents)
87
- 3. The complexity level (1-5, where 5 is most complex)
88
- 4. Any key topics or technologies mentioned
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="Match user queries to the most appropriate agent based on specializations.",
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.primary_specialization,
103
- "secondary_specializations": analysis.secondary_specializations,
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
- return next(iter(ai_agents.keys()))
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