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
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ class AudioTranscoder(ABC):
7
+ """Abstract audio transcoder for converting between compressed audio and PCM16.
8
+
9
+ Implementations may rely on external tools (e.g., ffmpeg).
10
+ """
11
+
12
+ @abstractmethod
13
+ async def to_pcm16(
14
+ self, audio_bytes: bytes, input_mime: str, rate_hz: int
15
+ ) -> bytes:
16
+ """Transcode arbitrary audio to mono PCM16LE at rate_hz.
17
+
18
+ Args:
19
+ audio_bytes: Source audio bytes (e.g., MP4/AAC)
20
+ input_mime: Source mime-type (e.g., 'audio/mp4', 'audio/aac')
21
+ rate_hz: Target sample rate
22
+ Returns:
23
+ Raw PCM16LE mono bytes at the given rate
24
+ """
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ async def from_pcm16(
29
+ self, pcm16_bytes: bytes, output_mime: str, rate_hz: int
30
+ ) -> bytes:
31
+ """Transcode mono PCM16LE at rate_hz to the desired output mime.
32
+
33
+ Args:
34
+ pcm16_bytes: Raw PCM16LE bytes
35
+ output_mime: Target mime-type (e.g., 'audio/aac')
36
+ rate_hz: Sample rate of the PCM
37
+ Returns:
38
+ Encoded audio bytes
39
+ """
40
+ raise NotImplementedError
@@ -27,13 +27,20 @@ class DataStorageProvider(ABC):
27
27
 
28
28
  @abstractmethod
29
29
  def find(
30
- self, collection: str, query: Dict, sort: Optional[List] = None, limit: int = 0, skip: int = 0
30
+ self,
31
+ collection: str,
32
+ query: Dict,
33
+ sort: Optional[List] = None,
34
+ limit: int = 0,
35
+ skip: int = 0,
31
36
  ) -> List[Dict]:
32
37
  """Find documents matching query."""
33
38
  pass
34
39
 
35
40
  @abstractmethod
36
- def update_one(self, collection: str, query: Dict, update: Dict, upsert: bool = False) -> bool:
41
+ def update_one(
42
+ self, collection: str, query: Dict, update: Dict, upsert: bool = False
43
+ ) -> bool:
37
44
  """Update a document."""
38
45
  pass
39
46
 
@@ -1,10 +1,20 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import AsyncGenerator, List, Literal, Type, TypeVar, Union
2
+ from typing import (
3
+ Any,
4
+ AsyncGenerator,
5
+ Dict,
6
+ List,
7
+ Literal,
8
+ Optional,
9
+ Type,
10
+ TypeVar,
11
+ Union,
12
+ )
3
13
 
4
14
  from pydantic import BaseModel
5
15
 
6
16
 
7
- T = TypeVar('T', bound=BaseModel)
17
+ T = TypeVar("T", bound=BaseModel)
8
18
 
9
19
 
10
20
  class LLMProvider(ABC):
@@ -15,13 +25,43 @@ class LLMProvider(ABC):
15
25
  self,
16
26
  prompt: str,
17
27
  system_prompt: str = "",
18
- ) -> AsyncGenerator[str, None]:
28
+ api_key: Optional[str] = None,
29
+ base_url: Optional[str] = None,
30
+ model: Optional[str] = None,
31
+ tools: Optional[List[Dict[str, Any]]] = None,
32
+ ) -> Any:
19
33
  """Generate text from the language model."""
20
34
  pass
21
35
 
36
+ @abstractmethod
37
+ async def chat_stream(
38
+ self,
39
+ messages: List[Dict[str, Any]],
40
+ model: Optional[str] = None,
41
+ tools: Optional[List[Dict[str, Any]]] = None,
42
+ api_key: Optional[str] = None,
43
+ base_url: Optional[str] = None,
44
+ ) -> AsyncGenerator[Dict[str, Any], None]:
45
+ """Stream chat completion deltas and tool call deltas.
46
+
47
+ Yields normalized events:
48
+ - {"type": "content", "delta": str}
49
+ - {"type": "tool_call_delta", "id": Optional[str], "index": Optional[int], "name": Optional[str], "arguments_delta": str}
50
+ - {"type": "message_end", "finish_reason": str}
51
+ - {"type": "error", "error": str}
52
+ """
53
+ pass
54
+
22
55
  @abstractmethod
23
56
  async def parse_structured_output(
24
- self, prompt: str, system_prompt: str, model_class: Type[T],
57
+ self,
58
+ prompt: str,
59
+ system_prompt: str,
60
+ model_class: Type[T],
61
+ api_key: Optional[str] = None,
62
+ base_url: Optional[str] = None,
63
+ model: Optional[str] = None,
64
+ tools: Optional[List[Dict[str, Any]]] = None,
25
65
  ) -> T:
26
66
  """Generate structured output using a specific model class."""
27
67
  pass
@@ -30,11 +70,19 @@ class LLMProvider(ABC):
30
70
  async def tts(
31
71
  self,
32
72
  text: str,
33
- instructions: str = "",
34
- voice: Literal["alloy", "ash", "ballad", "coral", "echo",
35
- "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
36
- response_format: Literal['mp3', 'opus',
37
- 'aac', 'flac', 'wav', 'pcm'] = "aac",
73
+ voice: Literal[
74
+ "alloy",
75
+ "ash",
76
+ "ballad",
77
+ "coral",
78
+ "echo",
79
+ "fable",
80
+ "onyx",
81
+ "nova",
82
+ "sage",
83
+ "shimmer",
84
+ ] = "nova",
85
+ response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] = "aac",
38
86
  ) -> AsyncGenerator[bytes, None]:
39
87
  """Stream text-to-speech audio from the language model."""
40
88
  pass
@@ -49,3 +97,32 @@ class LLMProvider(ABC):
49
97
  ) -> AsyncGenerator[str, None]:
50
98
  """Transcribe audio from the language model."""
51
99
  pass
100
+
101
+ @abstractmethod
102
+ async def embed_text(
103
+ self, text: str, model: Optional[str] = None, dimensions: Optional[int] = None
104
+ ) -> List[float]:
105
+ """
106
+ Generate an embedding for the given text.
107
+
108
+ Args:
109
+ text: The text to embed.
110
+ model: The embedding model to use.
111
+ dimensions: Optional desired output dimensions for the embedding.
112
+
113
+ Returns:
114
+ A list of floats representing the embedding vector.
115
+ """
116
+ pass
117
+
118
+ @abstractmethod
119
+ async def generate_text_with_images(
120
+ self,
121
+ prompt: str,
122
+ images: List[Union[str, bytes]],
123
+ system_prompt: str = "",
124
+ detail: Literal["low", "high", "auto"] = "auto",
125
+ tools: Optional[List[Dict[str, Any]]] = None,
126
+ ) -> str:
127
+ """Generate text from the language model using images."""
128
+ pass
@@ -27,7 +27,7 @@ class MemoryProvider(ABC):
27
27
  query: Dict,
28
28
  sort: Optional[List[Tuple]] = None,
29
29
  limit: int = 0,
30
- skip: int = 0
30
+ skip: int = 0,
31
31
  ) -> List[Dict]:
32
32
  """Find documents matching query."""
33
33
  pass
@@ -36,3 +36,15 @@ class MemoryProvider(ABC):
36
36
  def count_documents(self, collection: str, query: Dict) -> int:
37
37
  """Count documents matching query."""
38
38
  pass
39
+
40
+ @abstractmethod
41
+ async def save_capture(
42
+ self,
43
+ user_id: str,
44
+ capture_name: str,
45
+ agent_name: Optional[str],
46
+ data: Dict[str, Any],
47
+ schema: Optional[Dict[str, Any]] = None,
48
+ ) -> Optional[str]:
49
+ """Persist a structured capture for a user and return its ID if available."""
50
+ pass
@@ -0,0 +1,212 @@
1
+ from __future__ import annotations
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass
4
+ from typing import (
5
+ Any,
6
+ AsyncGenerator,
7
+ Dict,
8
+ Literal,
9
+ Optional,
10
+ Awaitable,
11
+ Callable,
12
+ List,
13
+ Union,
14
+ )
15
+
16
+
17
+ @dataclass
18
+ class RealtimeSessionOptions:
19
+ model: Optional[str] = None
20
+ voice: Literal[
21
+ "alloy",
22
+ "ash",
23
+ "ballad",
24
+ "cedar",
25
+ "coral",
26
+ "echo",
27
+ "marin",
28
+ "sage",
29
+ "shimmer",
30
+ "verse",
31
+ ] = "marin"
32
+ vad_enabled: bool = True
33
+ input_rate_hz: int = 24000
34
+ output_rate_hz: int = 24000
35
+ input_mime: str = "audio/pcm" # 16-bit PCM
36
+ output_mime: str = "audio/pcm" # 16-bit PCM
37
+ output_modalities: List[Literal["audio", "text"]] = None # None means auto-detect
38
+ instructions: Optional[str] = None
39
+ # Optional: tools payload compatible with OpenAI Realtime session.update
40
+ tools: Optional[list[dict[str, Any]]] = None
41
+ tool_choice: str = "auto"
42
+ # Tool execution behavior
43
+ # Max time to allow a tool to run before timing out (seconds)
44
+ tool_timeout_s: float = 300.0
45
+ # Optional guard: if a tool takes longer than this to complete, skip sending
46
+ # function_call_output to avoid stale/expired call_id issues. Set to None to always send.
47
+ tool_result_max_age_s: Optional[float] = None
48
+ # --- Realtime transcription configuration (optional) ---
49
+ # When transcription_model is set, QueryService should skip the HTTP STT path and rely on
50
+ # realtime websocket transcription events. Other fields customize that behavior.
51
+ transcription_model: Optional[str] = None
52
+ transcription_language: Optional[str] = None # e.g. 'en'
53
+ transcription_prompt: Optional[str] = None
54
+ transcription_noise_reduction: Optional[bool] = None
55
+ transcription_include_logprobs: bool = False
56
+
57
+
58
+ @dataclass
59
+ class RealtimeChunk:
60
+ """Represents a chunk of data from a realtime session with its modality type."""
61
+
62
+ modality: Literal["audio", "text"]
63
+ data: Union[str, bytes]
64
+ timestamp: Optional[float] = None # Optional timestamp for ordering
65
+ metadata: Optional[Dict[str, Any]] = None # Optional additional metadata
66
+
67
+ @property
68
+ def is_audio(self) -> bool:
69
+ """Check if this is an audio chunk."""
70
+ return self.modality == "audio"
71
+
72
+ @property
73
+ def is_text(self) -> bool:
74
+ """Check if this is a text chunk."""
75
+ return self.modality == "text"
76
+
77
+ @property
78
+ def text_data(self) -> Optional[str]:
79
+ """Get text data if this is a text chunk."""
80
+ return self.data if isinstance(self.data, str) else None
81
+
82
+ @property
83
+ def audio_data(self) -> Optional[bytes]:
84
+ """Get audio data if this is an audio chunk."""
85
+ return self.data if isinstance(self.data, bytes) else None
86
+
87
+
88
+ async def separate_audio_chunks(
89
+ chunks: AsyncGenerator[RealtimeChunk, None],
90
+ ) -> AsyncGenerator[bytes, None]:
91
+ """Extract only audio chunks from a stream of RealtimeChunk objects.
92
+
93
+ Args:
94
+ chunks: Stream of RealtimeChunk objects
95
+
96
+ Yields:
97
+ Audio data bytes from audio chunks only
98
+ """
99
+ async for chunk in chunks:
100
+ if chunk.is_audio and chunk.audio_data:
101
+ yield chunk.audio_data
102
+
103
+
104
+ async def separate_text_chunks(
105
+ chunks: AsyncGenerator[RealtimeChunk, None],
106
+ ) -> AsyncGenerator[str, None]:
107
+ """Extract only text chunks from a stream of RealtimeChunk objects.
108
+
109
+ Args:
110
+ chunks: Stream of RealtimeChunk objects
111
+
112
+ Yields:
113
+ Text data from text chunks only
114
+ """
115
+ async for chunk in chunks:
116
+ if chunk.is_text and chunk.text_data:
117
+ yield chunk.text_data
118
+
119
+
120
+ async def demux_realtime_chunks(
121
+ chunks: AsyncGenerator[RealtimeChunk, None],
122
+ ) -> tuple[AsyncGenerator[bytes, None], AsyncGenerator[str, None]]:
123
+ """Demux a stream of RealtimeChunk objects into separate audio and text streams.
124
+
125
+ Note: This function consumes the input generator, so each output stream can only be consumed once.
126
+
127
+ Args:
128
+ chunks: Stream of RealtimeChunk objects
129
+
130
+ Returns:
131
+ Tuple of (audio_stream, text_stream) async generators
132
+ """
133
+ # Collect all chunks first since we can't consume the generator twice
134
+ collected_chunks = []
135
+ async for chunk in chunks:
136
+ collected_chunks.append(chunk)
137
+
138
+ async def audio_stream():
139
+ for chunk in collected_chunks:
140
+ if chunk.is_audio and chunk.audio_data:
141
+ yield chunk.audio_data
142
+
143
+ async def text_stream():
144
+ for chunk in collected_chunks:
145
+ if chunk.is_text and chunk.text_data:
146
+ yield chunk.text_data
147
+
148
+ return audio_stream(), text_stream()
149
+
150
+
151
+ class BaseRealtimeSession(ABC):
152
+ """Abstract realtime session supporting bidirectional audio/text over WebSocket."""
153
+
154
+ @abstractmethod
155
+ async def connect(self) -> None: # pragma: no cover
156
+ pass
157
+
158
+ @abstractmethod
159
+ async def close(self) -> None: # pragma: no cover
160
+ pass
161
+
162
+ # --- Client events ---
163
+ @abstractmethod
164
+ async def update_session(
165
+ self, session_patch: Dict[str, Any]
166
+ ) -> None: # pragma: no cover
167
+ pass
168
+
169
+ @abstractmethod
170
+ async def append_audio(self, pcm16_bytes: bytes) -> None: # pragma: no cover
171
+ """Append 16-bit PCM audio bytes (matching configured input rate/mime)."""
172
+ pass
173
+
174
+ @abstractmethod
175
+ async def commit_input(self) -> None: # pragma: no cover
176
+ pass
177
+
178
+ @abstractmethod
179
+ async def clear_input(self) -> None: # pragma: no cover
180
+ pass
181
+
182
+ @abstractmethod
183
+ async def create_response(
184
+ self, response_patch: Optional[Dict[str, Any]] = None
185
+ ) -> None: # pragma: no cover
186
+ pass
187
+
188
+ # --- Server events (demuxed) ---
189
+ @abstractmethod
190
+ def iter_events(self) -> AsyncGenerator[Dict[str, Any], None]: # pragma: no cover
191
+ pass
192
+
193
+ @abstractmethod
194
+ def iter_output_audio(self) -> AsyncGenerator[bytes, None]: # pragma: no cover
195
+ pass
196
+
197
+ @abstractmethod
198
+ def iter_input_transcript(self) -> AsyncGenerator[str, None]: # pragma: no cover
199
+ pass
200
+
201
+ @abstractmethod
202
+ def iter_output_transcript(self) -> AsyncGenerator[str, None]: # pragma: no cover
203
+ pass
204
+
205
+ # --- Optional tool execution hook ---
206
+ @abstractmethod
207
+ def set_tool_executor(
208
+ self,
209
+ executor: Callable[[str, Dict[str, Any]], Awaitable[Dict[str, Any]]],
210
+ ) -> None: # pragma: no cover
211
+ """Register a coroutine that executes a tool by name with arguments and returns a result dict."""
212
+ pass
@@ -0,0 +1,53 @@
1
+ from typing import List, Dict, Any, Optional
2
+
3
+
4
+ class VectorStorageProvider:
5
+ """Interface for Vector Storage Providers."""
6
+
7
+ async def upsert(
8
+ self, vectors: List[Dict[str, Any]], namespace: Optional[str] = None
9
+ ) -> None:
10
+ """Upsert vectors into the storage."""
11
+ pass
12
+
13
+ async def upsert_text(
14
+ self,
15
+ texts: List[str],
16
+ ids: List[str],
17
+ metadatas: Optional[List[Dict[str, Any]]] = None,
18
+ namespace: Optional[str] = None,
19
+ ) -> None:
20
+ """Embeds texts and upserts them into the storage."""
21
+ pass
22
+
23
+ async def query(
24
+ self,
25
+ vector: List[float],
26
+ top_k: int = 5,
27
+ namespace: Optional[str] = None,
28
+ filter: Optional[Dict[str, Any]] = None,
29
+ include_values: bool = False,
30
+ include_metadata: bool = True,
31
+ ) -> List[Dict[str, Any]]:
32
+ """Query for similar vectors."""
33
+ pass
34
+
35
+ async def query_text(
36
+ self,
37
+ query_text: str,
38
+ top_k: Optional[int] = None,
39
+ namespace: Optional[str] = None,
40
+ filter: Optional[Dict[str, Any]] = None,
41
+ include_values: bool = False,
42
+ include_metadata: bool = True,
43
+ ) -> List[Dict[str, Any]]:
44
+ """Embeds query text and queries the storage."""
45
+ pass
46
+
47
+ async def delete(self, ids: List[str], namespace: Optional[str] = None) -> None:
48
+ """Delete vectors by IDs."""
49
+ pass
50
+
51
+ async def describe_index_stats(self) -> Dict[str, Any]:
52
+ """Get statistics about the index/collection."""
53
+ pass
@@ -1,5 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Any, AsyncGenerator, Dict, List, Literal, Optional, Union
2
+ from typing import Any, AsyncGenerator, Dict, List, Literal, Optional, Type, Union
3
+
4
+ from pydantic import BaseModel
3
5
 
4
6
  from solana_agent.domains.agent import AIAgent
5
7
 
@@ -8,7 +10,9 @@ class AgentService(ABC):
8
10
  """Interface for agent management and response generation."""
9
11
 
10
12
  @abstractmethod
11
- def register_ai_agent(self, name: str, instructions: str, specialization: str) -> None:
13
+ def register_ai_agent(
14
+ self, name: str, instructions: str, specialization: str
15
+ ) -> None:
12
16
  """Register an AI agent with its specialization."""
13
17
  pass
14
18
 
@@ -25,16 +29,25 @@ class AgentService(ABC):
25
29
  query: Union[str, bytes],
26
30
  memory_context: str = "",
27
31
  output_format: Literal["text", "audio"] = "text",
28
- audio_voice: Literal["alloy", "ash", "ballad", "coral", "echo",
29
- "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
30
- audio_instructions: Optional[str] = None,
31
- audio_output_format: Literal['mp3', 'opus',
32
- 'aac', 'flac', 'wav', 'pcm'] = "aac",
33
- audio_input_format: Literal[
34
- "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
35
- ] = "mp4",
32
+ audio_voice: Literal[
33
+ "alloy",
34
+ "ash",
35
+ "ballad",
36
+ "coral",
37
+ "echo",
38
+ "fable",
39
+ "onyx",
40
+ "nova",
41
+ "sage",
42
+ "shimmer",
43
+ ] = "nova",
44
+ audio_output_format: Literal[
45
+ "mp3", "opus", "aac", "flac", "wav", "pcm"
46
+ ] = "aac",
36
47
  prompt: Optional[str] = None,
37
- ) -> AsyncGenerator[Union[str, bytes], None]:
48
+ images: Optional[List[Union[str, bytes]]] = None,
49
+ output_model: Optional[Type[BaseModel]] = None,
50
+ ) -> AsyncGenerator[Union[str, bytes, BaseModel], None]:
38
51
  """Generate a response from an agent."""
39
52
  pass
40
53
 
@@ -49,7 +62,9 @@ class AgentService(ABC):
49
62
  pass
50
63
 
51
64
  @abstractmethod
52
- async def execute_tool(self, agent_name: str, tool_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
65
+ async def execute_tool(
66
+ self, agent_name: str, tool_name: str, parameters: Dict[str, Any]
67
+ ) -> Dict[str, Any]:
53
68
  """Execute a tool on behalf of an agent."""
54
69
  pass
55
70
 
@@ -0,0 +1,59 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Dict, List, Any, Optional, Union
3
+
4
+
5
+ class KnowledgeBaseService(ABC):
6
+ """
7
+ Interface for a Knowledge Base service.
8
+ """
9
+
10
+ @abstractmethod
11
+ async def add_document(
12
+ self,
13
+ text: str,
14
+ metadata: Dict[str, Any],
15
+ document_id: Optional[str] = None,
16
+ namespace: Optional[str] = None,
17
+ ) -> str:
18
+ """
19
+ Add a document to the knowledge base.
20
+ """
21
+ pass
22
+
23
+ @abstractmethod
24
+ async def query(
25
+ self,
26
+ query_text: str,
27
+ filter: Optional[Dict[str, Any]] = None,
28
+ top_k: int = 5,
29
+ namespace: Optional[str] = None,
30
+ include_content: bool = True,
31
+ include_metadata: bool = True,
32
+ ) -> List[Dict[str, Any]]:
33
+ """
34
+ Query the knowledge base with semantic search.
35
+ """
36
+ pass
37
+
38
+ @abstractmethod
39
+ async def delete_document(
40
+ self, document_id: str, namespace: Optional[str] = None
41
+ ) -> bool:
42
+ """
43
+ Delete a document from the knowledge base.
44
+ """
45
+ pass
46
+
47
+ @abstractmethod
48
+ async def add_pdf_document(
49
+ self,
50
+ pdf_data: Union[bytes, str], # PDF bytes or file path
51
+ metadata: Dict[str, Any],
52
+ document_id: Optional[str] = None,
53
+ namespace: Optional[str] = None,
54
+ chunk_batch_size: int = 50,
55
+ ) -> str:
56
+ """
57
+ Add a PDF document to the knowledge base.
58
+ """
59
+ pass
@@ -1,5 +1,10 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Any, AsyncGenerator, Dict, Literal, Optional, Union
2
+ from typing import Any, AsyncGenerator, Dict, List, Literal, Optional, Type, Union
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from solana_agent.interfaces.services.routing import RoutingService as RoutingInterface
7
+ from solana_agent.interfaces.providers.realtime import RealtimeChunk
3
8
 
4
9
 
5
10
  class QueryService(ABC):
@@ -11,16 +16,44 @@ class QueryService(ABC):
11
16
  user_id: str,
12
17
  query: Union[str, bytes],
13
18
  output_format: Literal["text", "audio"] = "text",
14
- audio_voice: Literal["alloy", "ash", "ballad", "coral", "echo",
15
- "fable", "onyx", "nova", "sage", "shimmer"] = "nova",
16
- audio_instructions: Optional[str] = None,
17
- audio_output_format: Literal['mp3', 'opus',
18
- 'aac', 'flac', 'wav', 'pcm'] = "aac",
19
+ rt_output_modalities: Optional[List[Literal["audio", "text"]]] = None,
20
+ rt_voice: Literal[
21
+ "alloy",
22
+ "ash",
23
+ "ballad",
24
+ "cedar",
25
+ "coral",
26
+ "echo",
27
+ "marin",
28
+ "sage",
29
+ "shimmer",
30
+ "verse",
31
+ ] = "marin",
32
+ audio_voice: Literal[
33
+ "alloy",
34
+ "ash",
35
+ "ballad",
36
+ "coral",
37
+ "echo",
38
+ "fable",
39
+ "onyx",
40
+ "nova",
41
+ "sage",
42
+ "shimmer",
43
+ ] = "nova",
44
+ audio_output_format: Literal[
45
+ "mp3", "opus", "aac", "flac", "wav", "pcm"
46
+ ] = "aac",
19
47
  audio_input_format: Literal[
20
48
  "flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
21
49
  ] = "mp4",
22
50
  prompt: Optional[str] = None,
23
- ) -> AsyncGenerator[Union[str, bytes], None]:
51
+ router: Optional[RoutingInterface] = None,
52
+ images: Optional[List[Union[str, bytes]]] = None,
53
+ output_model: Optional[Type[BaseModel]] = None,
54
+ capture_schema: Optional[Dict[str, Any]] = None,
55
+ capture_name: Optional[str] = None,
56
+ ) -> AsyncGenerator[Union[str, bytes, BaseModel, RealtimeChunk], None]:
24
57
  """Process the user request and generate a response."""
25
58
  pass
26
59
 
@@ -30,7 +63,7 @@ class QueryService(ABC):
30
63
  user_id: str,
31
64
  page_num: int = 1,
32
65
  page_size: int = 20,
33
- sort_order: str = "desc" # "asc" for oldest-first, "desc" for newest-first
66
+ sort_order: str = "desc", # "asc" for oldest-first, "desc" for newest-first
34
67
  ) -> Dict[str, Any]:
35
68
  """Get paginated message history for a user."""
36
69
  pass
@@ -1,5 +1,4 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Any, Tuple
3
2
 
4
3
 
5
4
  class RoutingService(ABC):