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
|
@@ -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,
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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(
|
|
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[
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|