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
@@ -4,14 +4,19 @@ Simplified client interface for interacting with the Solana Agent system.
4
4
  This module provides a clean API for end users to interact with
5
5
  the agent system without dealing with internal implementation details.
6
6
  """
7
+
7
8
  import json
8
9
  import importlib.util
9
- from typing import AsyncGenerator, Dict, Any, Literal, Optional, Union
10
+ from typing import AsyncGenerator, Dict, Any, List, Literal, Optional, Type, Union
11
+
12
+ from pydantic import BaseModel
10
13
 
11
14
  from solana_agent.factories.agent_factory import SolanaAgentFactory
12
15
  from solana_agent.interfaces.client.client import SolanaAgent as SolanaAgentInterface
13
16
  from solana_agent.interfaces.plugins.plugins import Tool
17
+ from solana_agent.services.knowledge_base import KnowledgeBaseService
14
18
  from solana_agent.interfaces.services.routing import RoutingService as RoutingInterface
19
+ from solana_agent.interfaces.providers.realtime import RealtimeChunk
15
20
 
16
21
 
17
22
  class SolanaAgent(SolanaAgentInterface):
@@ -33,8 +38,7 @@ class SolanaAgent(SolanaAgentInterface):
33
38
  config = json.load(f)
34
39
  else:
35
40
  # Assume it's a Python file
36
- spec = importlib.util.spec_from_file_location(
37
- "config", config_path)
41
+ spec = importlib.util.spec_from_file_location("config", config_path)
38
42
  config_module = importlib.util.module_from_spec(spec)
39
43
  spec.loader.exec_module(config_module)
40
44
  config = config_module.config
@@ -46,29 +50,72 @@ class SolanaAgent(SolanaAgentInterface):
46
50
  user_id: str,
47
51
  message: Union[str, bytes],
48
52
  prompt: Optional[str] = None,
53
+ capture_schema: Optional[Dict[str, Any]] = None,
54
+ capture_name: Optional[str] = None,
49
55
  output_format: Literal["text", "audio"] = "text",
50
- audio_voice: Literal["alloy", "ash", "ballad", "coral", "echo",
51
- "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
52
- audio_instructions: Optional[str] = None,
53
- audio_output_format: Literal['mp3', 'opus',
54
- 'aac', 'flac', 'wav', 'pcm'] = "aac",
56
+ # Realtime (WebSocket) options used when realtime=True
57
+ realtime: bool = False,
58
+ vad: Optional[bool] = False,
59
+ rt_encode_input: bool = False,
60
+ rt_encode_output: bool = False,
61
+ rt_output_modalities: Optional[List[Literal["audio", "text"]]] = None,
62
+ rt_voice: Literal[
63
+ "alloy",
64
+ "ash",
65
+ "ballad",
66
+ "cedar",
67
+ "coral",
68
+ "echo",
69
+ "marin",
70
+ "sage",
71
+ "shimmer",
72
+ "verse",
73
+ ] = "marin",
74
+ audio_voice: Literal[
75
+ "alloy",
76
+ "ash",
77
+ "ballad",
78
+ "coral",
79
+ "echo",
80
+ "fable",
81
+ "onyx",
82
+ "nova",
83
+ "sage",
84
+ "shimmer",
85
+ ] = "nova",
86
+ audio_output_format: Literal[
87
+ "mp3", "opus", "aac", "flac", "wav", "pcm"
88
+ ] = "aac",
55
89
  audio_input_format: Literal[
56
90
  "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
57
91
  ] = "mp4",
58
92
  router: Optional[RoutingInterface] = None,
59
- ) -> AsyncGenerator[Union[str, bytes], None]: # pragma: no cover
60
- """Process a user message and return the response stream.
93
+ images: Optional[List[Union[str, bytes]]] = None,
94
+ output_model: Optional[Type[BaseModel]] = None,
95
+ ) -> AsyncGenerator[
96
+ Union[str, bytes, BaseModel, RealtimeChunk], None
97
+ ]: # pragma: no cover
98
+ """Process a user message (text or audio) and optional images, returning the response stream.
61
99
 
62
100
  Args:
63
101
  user_id: User ID
64
102
  message: Text message or audio bytes
65
103
  prompt: Optional prompt for the agent
66
104
  output_format: Response format ("text" or "audio")
105
+ capture_schema: Optional Pydantic schema for structured output
106
+ capture_name: Optional name for structured output capture
107
+ realtime: Whether to use realtime (WebSocket) processing
108
+ vad: Whether to use voice activity detection (for audio input)
109
+ rt_encode_input: Whether to re-encode input audio for compatibility
110
+ rt_encode_output: Whether to re-encode output audio for compatibility
111
+ rt_output_modalities: Modalities to return in realtime (default both if None)
112
+ rt_voice: Voice to use for realtime audio output
67
113
  audio_voice: Voice to use for audio output
68
- audio_instructions: Not used currently
69
114
  audio_output_format: Audio output format
70
115
  audio_input_format: Audio input format
71
116
  router: Optional routing service for processing
117
+ images: Optional list of image URLs (str) or image bytes.
118
+ output_model: Optional Pydantic model for structured output
72
119
 
73
120
  Returns:
74
121
  Async generator yielding response chunks (text strings or audio bytes)
@@ -76,13 +123,22 @@ class SolanaAgent(SolanaAgentInterface):
76
123
  async for chunk in self.query_service.process(
77
124
  user_id=user_id,
78
125
  query=message,
126
+ images=images,
79
127
  output_format=output_format,
128
+ realtime=realtime,
129
+ vad=vad,
130
+ rt_encode_input=rt_encode_input,
131
+ rt_encode_output=rt_encode_output,
132
+ rt_output_modalities=rt_output_modalities,
133
+ rt_voice=rt_voice,
80
134
  audio_voice=audio_voice,
81
- audio_instructions=audio_instructions,
82
135
  audio_output_format=audio_output_format,
83
136
  audio_input_format=audio_input_format,
84
137
  prompt=prompt,
85
138
  router=router,
139
+ output_model=output_model,
140
+ capture_schema=capture_schema,
141
+ capture_name=capture_name,
86
142
  ):
87
143
  yield chunk
88
144
 
@@ -100,7 +156,7 @@ class SolanaAgent(SolanaAgentInterface):
100
156
  user_id: str,
101
157
  page_num: int = 1,
102
158
  page_size: int = 20,
103
- sort_order: str = "desc" # "asc" for oldest-first, "desc" for newest-first
159
+ sort_order: str = "desc", # "asc" for oldest-first, "desc" for newest-first
104
160
  ) -> Dict[str, Any]: # pragma: no cover
105
161
  """
106
162
  Get paginated message history for a user.
@@ -118,21 +174,125 @@ class SolanaAgent(SolanaAgentInterface):
118
174
  user_id, page_num, page_size, sort_order
119
175
  )
120
176
 
121
- def register_tool(self, tool: Tool) -> bool:
177
+ def register_tool(self, agent_name: str, tool: Tool) -> bool:
122
178
  """
123
179
  Register a tool with the agent system.
124
180
 
125
181
  Args:
182
+ agent_name: Name of the agent to register the tool with
126
183
  tool: Tool instance to register
127
184
 
128
185
  Returns:
129
186
  True if successful, False
130
187
  """
131
- success = self.query_service.agent_service.tool_registry.register_tool(
132
- tool)
188
+ success = self.query_service.agent_service.tool_registry.register_tool(tool)
133
189
  if success:
134
- agents = self.query_service.agent_service.get_all_ai_agents()
135
- for agent_name in agents:
136
- self.query_service.agent_service.assign_tool_for_agent(
137
- agent_name, tool.name)
190
+ self.query_service.agent_service.assign_tool_for_agent(
191
+ agent_name, tool.name
192
+ )
138
193
  return success
194
+
195
+ def _ensure_kb(self) -> KnowledgeBaseService:
196
+ """Checks if the knowledge base service is available and returns it."""
197
+ if (
198
+ hasattr(self.query_service, "knowledge_base")
199
+ and self.query_service.knowledge_base
200
+ ):
201
+ return self.query_service.knowledge_base
202
+ else:
203
+ raise AttributeError("Knowledge base service not configured or available.")
204
+
205
+ async def kb_add_document(
206
+ self,
207
+ text: str,
208
+ metadata: Dict[str, Any],
209
+ document_id: Optional[str] = None,
210
+ namespace: Optional[str] = None,
211
+ ) -> str:
212
+ """
213
+ Add a document to the knowledge base.
214
+
215
+ Args:
216
+ text: Document text content.
217
+ metadata: Document metadata.
218
+ document_id: Optional document ID.
219
+ namespace: Optional Pinecone namespace.
220
+
221
+ Returns:
222
+ The document ID.
223
+ """
224
+ kb = self._ensure_kb()
225
+ return await kb.add_document(text, metadata, document_id, namespace)
226
+
227
+ async def kb_query(
228
+ self,
229
+ query_text: str,
230
+ filter: Optional[Dict[str, Any]] = None,
231
+ top_k: int = 5,
232
+ namespace: Optional[str] = None,
233
+ include_content: bool = True,
234
+ include_metadata: bool = True,
235
+ ) -> List[Dict[str, Any]]:
236
+ """
237
+ Query the knowledge base.
238
+
239
+ Args:
240
+ query_text: Search query text.
241
+ filter: Optional filter criteria.
242
+ top_k: Maximum number of results.
243
+ namespace: Optional Pinecone namespace.
244
+ include_content: Include document content in results.
245
+ include_metadata: Include document metadata in results.
246
+
247
+ Returns:
248
+ List of matching documents.
249
+ """
250
+ kb = self._ensure_kb()
251
+ return await kb.query(
252
+ query_text, filter, top_k, namespace, include_content, include_metadata
253
+ )
254
+
255
+ async def kb_delete_document(
256
+ self, document_id: str, namespace: Optional[str] = None
257
+ ) -> bool:
258
+ """
259
+ Delete a document from the knowledge base.
260
+
261
+ Args:
262
+ document_id: ID of document to delete.
263
+ namespace: Optional Pinecone namespace.
264
+
265
+ Returns:
266
+ True if successful.
267
+ """
268
+ kb = self._ensure_kb()
269
+ return await kb.delete_document(document_id, namespace)
270
+
271
+ async def kb_add_pdf_document(
272
+ self,
273
+ pdf_data: Union[bytes, str],
274
+ metadata: Dict[str, Any],
275
+ document_id: Optional[str] = None,
276
+ namespace: Optional[str] = None,
277
+ chunk_batch_size: int = 50,
278
+ ) -> str:
279
+ """
280
+ Add a PDF document to the knowledge base via the client.
281
+
282
+ Args:
283
+ pdf_data: PDF content as bytes or a path to the PDF file.
284
+ metadata: Document metadata.
285
+ document_id: Optional parent document ID.
286
+ namespace: Optional Pinecone namespace for chunks.
287
+ chunk_batch_size: Batch size for upserting chunks.
288
+
289
+ Returns:
290
+ The parent document ID.
291
+ """
292
+ kb = self._ensure_kb()
293
+ # Type check added for clarity, though handled in service
294
+ if not isinstance(pdf_data, (bytes, str)):
295
+ raise TypeError("pdf_data must be bytes or a file path string.")
296
+ return await kb.add_pdf_document(
297
+ pdf_data, metadata, document_id, namespace, chunk_batch_size
298
+ )
@@ -4,25 +4,20 @@ Domain models for AI and human agents.
4
4
  This module defines the core domain models for representing
5
5
  AI agents, human agents, and business mission/values.
6
6
  """
7
- from typing import List, Dict, Any
7
+
8
+ from typing import List, Dict, Optional, Any
8
9
  from pydantic import BaseModel, Field, field_validator
9
10
 
10
11
 
11
12
  class BusinessMission(BaseModel):
12
13
  """Business mission and values to guide agent behavior."""
13
14
 
14
- mission: str = Field(...,
15
- description="Business mission statement")
15
+ mission: str = Field(..., description="Business mission statement")
16
16
  values: List[Dict[str, str]] = Field(
17
- default_factory=list,
18
- description="Business values as name-description pairs"
19
- )
20
- goals: List[str] = Field(
21
- default_factory=list,
22
- description="Business goals"
17
+ default_factory=list, description="Business values as name-description pairs"
23
18
  )
24
- voice: str = Field(
25
- None, description="Business voice or tone")
19
+ goals: List[str] = Field(default_factory=list, description="Business goals")
20
+ voice: str = Field(None, description="Business voice or tone")
26
21
 
27
22
  @field_validator("mission")
28
23
  @classmethod
@@ -56,9 +51,14 @@ class AIAgent(BaseModel):
56
51
  model_config = {"arbitrary_types_allowed": True}
57
52
 
58
53
  name: str = Field(..., description="Unique agent identifier name")
59
- instructions: str = Field(...,
60
- description="Base instructions for the agent")
54
+ instructions: str = Field(..., description="Base instructions for the agent")
61
55
  specialization: str = Field(..., description="Agent's specialized domain")
56
+ capture_name: Optional[str] = Field(
57
+ default=None, description="Optional capture name for structured data"
58
+ )
59
+ capture_schema: Optional[Dict[str, Any]] = Field(
60
+ default=None, description="Optional JSON schema for structured capture"
61
+ )
62
62
 
63
63
  @field_validator("name", "specialization")
64
64
  @classmethod
@@ -4,11 +4,21 @@ from pydantic import BaseModel, Field
4
4
 
5
5
  class QueryAnalysis(BaseModel):
6
6
  """Analysis of a user query for routing purposes."""
7
- primary_specialization: str = Field(...,
8
- description="Main specialization needed")
9
- secondary_specializations: List[str] = Field(
10
- ..., description="Other helpful specializations")
11
- complexity_level: int = Field(...,
12
- description="Complexity level (1-5)")
13
- topics: List[str] = Field(..., description="Key topics in the query")
14
- confidence: float = Field(..., description="Confidence in the analysis")
7
+
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
+ default_factory=list,
14
+ description="Names of secondary agents that might be helpful (must be from the available agent names)",
15
+ )
16
+ complexity_level: int = Field(
17
+ default=1, description="Complexity level (1-5)", ge=1, le=5
18
+ )
19
+ topics: List[str] = Field(
20
+ default_factory=list, description="Key topics in the query"
21
+ )
22
+ confidence: float = Field(
23
+ default=0.5, description="Confidence in the analysis", ge=0.0, le=1.0
24
+ )