solana-agent 24.0.0__tar.gz → 24.1.1__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-24.0.0 → solana_agent-24.1.1}/PKG-INFO +3 -1
- {solana_agent-24.0.0 → solana_agent-24.1.1}/README.md +2 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/pyproject.toml +1 -1
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/adapters/llm_adapter.py +10 -10
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/providers/llm.py +11 -1
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/services/query.py +3 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/repositories/memory.py +2 -2
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/services/agent.py +257 -152
- {solana_agent-24.0.0 → solana_agent-24.1.1}/LICENSE +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/adapters/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/adapters/mongodb_adapter.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/client/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/client/solana_agent.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/domains/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/domains/agent.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/domains/routing.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/factories/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/factories/agent_factory.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/client/client.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/plugins/plugins.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/providers/data_storage.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/providers/memory.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/services/agent.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/services/routing.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/plugins/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/plugins/manager.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/plugins/registry.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/plugins/tools/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/plugins/tools/auto_tool.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/repositories/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/services/__init__.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/services/query.py +0 -0
- {solana_agent-24.0.0 → solana_agent-24.1.1}/solana_agent/services/routing.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: solana-agent
|
3
|
-
Version: 24.
|
3
|
+
Version: 24.1.1
|
4
4
|
Summary: Agentic IQ
|
5
5
|
License: MIT
|
6
6
|
Keywords: ai,openai,ai agents,agi
|
@@ -41,6 +41,7 @@ Build your AI business in three lines of code!
|
|
41
41
|
|
42
42
|
## Why?
|
43
43
|
* Three lines of code setup
|
44
|
+
* Fast Responses
|
44
45
|
* Multi-Agent Swarm
|
45
46
|
* Multi-Modal Streaming (Text & Audio)
|
46
47
|
* Conversational Memory & History
|
@@ -56,6 +57,7 @@ Build your AI business in three lines of code!
|
|
56
57
|
## Features
|
57
58
|
|
58
59
|
* Easy three lines of code setup
|
60
|
+
* Fast AI responses
|
59
61
|
* Designed for a multi-agent swarm
|
60
62
|
* Seamless text and audio streaming with real-time multi-modal processing
|
61
63
|
* Configurable audio voice characteristics via prompting
|
@@ -17,6 +17,7 @@ Build your AI business in three lines of code!
|
|
17
17
|
|
18
18
|
## Why?
|
19
19
|
* Three lines of code setup
|
20
|
+
* Fast Responses
|
20
21
|
* Multi-Agent Swarm
|
21
22
|
* Multi-Modal Streaming (Text & Audio)
|
22
23
|
* Conversational Memory & History
|
@@ -32,6 +33,7 @@ Build your AI business in three lines of code!
|
|
32
33
|
## Features
|
33
34
|
|
34
35
|
* Easy three lines of code setup
|
36
|
+
* Fast AI responses
|
35
37
|
* Designed for a multi-agent swarm
|
36
38
|
* Seamless text and audio streaming with real-time multi-modal processing
|
37
39
|
* Configurable audio voice characteristics via prompting
|
@@ -3,9 +3,9 @@ LLM provider adapters for the Solana Agent system.
|
|
3
3
|
|
4
4
|
These adapters implement the LLMProvider interface for different LLM services.
|
5
5
|
"""
|
6
|
-
from typing import AsyncGenerator,
|
6
|
+
from typing import AsyncGenerator, Literal, Type, TypeVar
|
7
7
|
|
8
|
-
from openai import
|
8
|
+
from openai import AsyncOpenAI
|
9
9
|
from pydantic import BaseModel
|
10
10
|
|
11
11
|
from solana_agent.interfaces.providers.llm import LLMProvider
|
@@ -17,7 +17,7 @@ class OpenAIAdapter(LLMProvider):
|
|
17
17
|
"""OpenAI implementation of LLMProvider with web search capabilities."""
|
18
18
|
|
19
19
|
def __init__(self, api_key: str):
|
20
|
-
self.client =
|
20
|
+
self.client = AsyncOpenAI(api_key=api_key)
|
21
21
|
self.parse_model = "gpt-4o-mini"
|
22
22
|
self.text_model = "gpt-4o-mini"
|
23
23
|
self.transcription_model = "gpt-4o-mini-transcribe"
|
@@ -44,7 +44,7 @@ class OpenAIAdapter(LLMProvider):
|
|
44
44
|
Audio bytes as they become available
|
45
45
|
"""
|
46
46
|
try:
|
47
|
-
with self.client.audio.speech.with_streaming_response.create(
|
47
|
+
async with self.client.audio.speech.with_streaming_response.create(
|
48
48
|
model=self.tts_model,
|
49
49
|
voice=voice,
|
50
50
|
instructions=instructions,
|
@@ -52,7 +52,7 @@ class OpenAIAdapter(LLMProvider):
|
|
52
52
|
response_format=response_format
|
53
53
|
) as stream:
|
54
54
|
# Stream the bytes in 16KB chunks
|
55
|
-
for chunk in stream.iter_bytes(chunk_size=1024 * 16):
|
55
|
+
async for chunk in stream.iter_bytes(chunk_size=1024 * 16):
|
56
56
|
yield chunk
|
57
57
|
|
58
58
|
except Exception as e:
|
@@ -84,13 +84,13 @@ class OpenAIAdapter(LLMProvider):
|
|
84
84
|
Transcript text chunks as they become available
|
85
85
|
"""
|
86
86
|
try:
|
87
|
-
with self.client.audio.transcriptions.with_streaming_response.create(
|
87
|
+
async with self.client.audio.transcriptions.with_streaming_response.create(
|
88
88
|
model=self.transcription_model,
|
89
89
|
file=(f"file.{input_format}", audio_bytes),
|
90
90
|
response_format="text",
|
91
91
|
) as stream:
|
92
92
|
# Stream the text in 16KB chunks
|
93
|
-
for chunk in stream.iter_text(chunk_size=1024 * 16):
|
93
|
+
async for chunk in stream.iter_text(chunk_size=1024 * 16):
|
94
94
|
yield chunk
|
95
95
|
|
96
96
|
except Exception as e:
|
@@ -119,9 +119,9 @@ class OpenAIAdapter(LLMProvider):
|
|
119
119
|
"model": self.text_model,
|
120
120
|
}
|
121
121
|
try:
|
122
|
-
response = self.client.chat.completions.create(**request_params)
|
122
|
+
response = await self.client.chat.completions.create(**request_params)
|
123
123
|
|
124
|
-
for chunk in response:
|
124
|
+
async for chunk in response:
|
125
125
|
if chunk.choices:
|
126
126
|
if chunk.choices[0].delta.content:
|
127
127
|
text = chunk.choices[0].delta.content
|
@@ -148,7 +148,7 @@ class OpenAIAdapter(LLMProvider):
|
|
148
148
|
|
149
149
|
try:
|
150
150
|
# First try the beta parsing API
|
151
|
-
completion = self.client.beta.chat.completions.parse(
|
151
|
+
completion = await self.client.beta.chat.completions.parse(
|
152
152
|
model=self.parse_model,
|
153
153
|
messages=messages,
|
154
154
|
response_format=model_class,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
from typing import AsyncGenerator, List, Literal, Type, TypeVar, Union
|
2
|
+
from typing import Any, AsyncGenerator, Callable, Dict, List, Literal, Optional, Type, TypeVar, Union
|
3
3
|
|
4
4
|
from pydantic import BaseModel
|
5
5
|
|
@@ -49,3 +49,13 @@ class LLMProvider(ABC):
|
|
49
49
|
) -> AsyncGenerator[str, None]:
|
50
50
|
"""Transcribe audio from the language model."""
|
51
51
|
pass
|
52
|
+
|
53
|
+
@abstractmethod
|
54
|
+
async def realtime_audio_transcription(
|
55
|
+
self,
|
56
|
+
audio_generator: AsyncGenerator[bytes, None],
|
57
|
+
transcription_config: Optional[Dict[str, Any]] = None,
|
58
|
+
on_event: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
59
|
+
) -> AsyncGenerator[str, None]:
|
60
|
+
"""Stream real-time audio transcription from the language model."""
|
61
|
+
pass
|
@@ -1,6 +1,8 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from typing import Any, AsyncGenerator, Dict, Literal, Optional, Union
|
3
3
|
|
4
|
+
from solana_agent.interfaces.services.routing import RoutingService as RoutingInterface
|
5
|
+
|
4
6
|
|
5
7
|
class QueryService(ABC):
|
6
8
|
"""Interface for processing user queries."""
|
@@ -20,6 +22,7 @@ class QueryService(ABC):
|
|
20
22
|
"flac", "mp3", "mp4", "mpeg", "mpga", "m4a", "ogg", "wav", "webm"
|
21
23
|
] = "mp4",
|
22
24
|
prompt: Optional[str] = None,
|
25
|
+
router: Optional[RoutingInterface] = None,
|
23
26
|
) -> AsyncGenerator[Union[str, bytes], None]:
|
24
27
|
"""Process the user request and generate a response."""
|
25
28
|
pass
|
@@ -69,8 +69,8 @@ class MemoryRepository(MemoryProvider):
|
|
69
69
|
# Store truncated messages
|
70
70
|
doc = {
|
71
71
|
"user_id": user_id,
|
72
|
-
"user_message":
|
73
|
-
"assistant_message":
|
72
|
+
"user_message": user_msg,
|
73
|
+
"assistant_message": assistant_msg,
|
74
74
|
"timestamp": datetime.now(timezone.utc)
|
75
75
|
}
|
76
76
|
self.mongo.insert_one(self.collection, doc)
|
@@ -191,7 +191,7 @@ class AgentService(AgentServiceInterface):
|
|
191
191
|
return
|
192
192
|
|
193
193
|
try:
|
194
|
-
# Handle audio input if provided
|
194
|
+
# Handle audio input if provided - KEEP REAL-TIME AUDIO TRANSCRIPTION
|
195
195
|
query_text = ""
|
196
196
|
if not isinstance(query, str):
|
197
197
|
async for transcript in self.llm_provider.transcribe_audio(query, input_format=audio_input_format):
|
@@ -209,118 +209,172 @@ class AgentService(AgentServiceInterface):
|
|
209
209
|
if prompt:
|
210
210
|
system_prompt += f"\n\nADDITIONAL PROMPT: {prompt}"
|
211
211
|
|
212
|
-
#
|
212
|
+
# Add tool usage prompt if tools are available
|
213
213
|
tool_calling_system_prompt = deepcopy(system_prompt)
|
214
214
|
if self.tool_registry:
|
215
215
|
tool_usage_prompt = self._get_tool_usage_prompt(agent_name)
|
216
216
|
if tool_usage_prompt:
|
217
217
|
tool_calling_system_prompt += f"\n\nTOOL CALLING PROMPT: {tool_usage_prompt}"
|
218
|
+
print(
|
219
|
+
f"Tools available to agent {agent_name}: {[t.get('name') for t in self.get_agent_tools(agent_name)]}")
|
218
220
|
|
219
|
-
# Variables for tracking the response
|
221
|
+
# Variables for tracking the complete response
|
220
222
|
complete_text_response = ""
|
221
|
-
|
222
|
-
# For audio output, we'll collect everything first
|
223
223
|
full_response_buffer = ""
|
224
224
|
|
225
|
-
# Variables for handling
|
226
|
-
|
227
|
-
|
225
|
+
# Variables for robust handling of tool call markers that may be split across chunks
|
226
|
+
tool_buffer = ""
|
227
|
+
pending_chunk = "" # To hold text that might contain partial markers
|
228
|
+
is_tool_call = False
|
229
|
+
window_size = 30 # Increased window size for better detection
|
230
|
+
|
231
|
+
# Define start and end markers
|
232
|
+
start_marker = "[TOOL]"
|
233
|
+
end_marker = "[/TOOL]"
|
228
234
|
|
229
|
-
# Generate and stream response
|
235
|
+
# Generate and stream response (ALWAYS use non-realtime for text generation)
|
236
|
+
print(
|
237
|
+
f"Generating response with {len(query_text)} characters of query text")
|
230
238
|
async for chunk in self.llm_provider.generate_text(
|
231
239
|
prompt=query_text,
|
232
240
|
system_prompt=tool_calling_system_prompt,
|
233
241
|
):
|
234
|
-
#
|
235
|
-
if
|
236
|
-
|
237
|
-
|
242
|
+
# If we have pending text from the previous chunk, combine it with this chunk
|
243
|
+
if pending_chunk:
|
244
|
+
combined_chunk = pending_chunk + chunk
|
245
|
+
pending_chunk = "" # Reset pending chunk
|
246
|
+
else:
|
247
|
+
combined_chunk = chunk
|
248
|
+
|
249
|
+
# STEP 1: Check for tool call start marker
|
250
|
+
if start_marker in combined_chunk and not is_tool_call:
|
251
|
+
print(
|
252
|
+
f"Found tool start marker in chunk of length {len(combined_chunk)}")
|
253
|
+
is_tool_call = True
|
254
|
+
|
255
|
+
# Extract text before the marker and the marker itself with everything after
|
256
|
+
start_pos = combined_chunk.find(start_marker)
|
257
|
+
before_marker = combined_chunk[:start_pos]
|
258
|
+
after_marker = combined_chunk[start_pos:]
|
259
|
+
|
260
|
+
# Yield text that appeared before the marker
|
261
|
+
if before_marker and output_format == "text":
|
262
|
+
yield before_marker
|
263
|
+
|
264
|
+
# Start collecting the tool call
|
265
|
+
tool_buffer = after_marker
|
266
|
+
continue # Skip to next chunk
|
267
|
+
|
268
|
+
# STEP 2: Handle ongoing tool call collection
|
269
|
+
if is_tool_call:
|
270
|
+
tool_buffer += combined_chunk
|
271
|
+
|
272
|
+
# Check if the tool call is complete
|
273
|
+
if end_marker in tool_buffer:
|
274
|
+
print(
|
275
|
+
f"Tool call complete, buffer size: {len(tool_buffer)}")
|
276
|
+
|
277
|
+
# Process the tool call
|
278
|
+
response_text = await self._handle_tool_call(
|
279
|
+
agent_name=agent_name,
|
280
|
+
tool_text=tool_buffer
|
281
|
+
)
|
282
|
+
|
283
|
+
# Clean the response to remove any markers or formatting
|
284
|
+
response_text = self._clean_tool_response(
|
285
|
+
response_text)
|
286
|
+
print(
|
287
|
+
f"Tool execution complete, result size: {len(response_text)}")
|
288
|
+
|
289
|
+
# Create new prompt with search/tool results
|
290
|
+
# Using "Search Result" instead of "TOOL RESPONSE" to avoid model repeating "TOOL"
|
291
|
+
user_prompt = f"{query_text}\n\nSearch Result: {response_text}"
|
292
|
+
tool_system_prompt = system_prompt + \
|
293
|
+
"\n DO NOT use the tool calling format again."
|
294
|
+
|
295
|
+
# Generate a new response with the tool results
|
296
|
+
print("Generating new response with tool results")
|
297
|
+
if output_format == "text":
|
298
|
+
# Stream the follow-up response for text output
|
299
|
+
async for processed_chunk in self.llm_provider.generate_text(
|
300
|
+
prompt=user_prompt,
|
301
|
+
system_prompt=tool_system_prompt,
|
302
|
+
):
|
303
|
+
complete_text_response += processed_chunk
|
304
|
+
yield processed_chunk
|
305
|
+
else:
|
306
|
+
# For audio output, collect the full response first
|
307
|
+
tool_response = ""
|
308
|
+
async for processed_chunk in self.llm_provider.generate_text(
|
309
|
+
prompt=user_prompt,
|
310
|
+
system_prompt=tool_system_prompt,
|
311
|
+
):
|
312
|
+
tool_response += processed_chunk
|
313
|
+
|
314
|
+
# Clean and add to our complete text record and audio buffer
|
315
|
+
tool_response = self._clean_for_audio(
|
316
|
+
tool_response)
|
317
|
+
complete_text_response += tool_response
|
318
|
+
full_response_buffer += tool_response
|
319
|
+
|
320
|
+
# Reset tool handling state
|
321
|
+
is_tool_call = False
|
322
|
+
tool_buffer = ""
|
323
|
+
pending_chunk = ""
|
324
|
+
break # Exit the original generation loop after tool processing
|
325
|
+
|
326
|
+
# Continue collecting tool call content without yielding
|
238
327
|
continue
|
239
328
|
|
240
|
-
#
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
)
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
prompt=user_prompt,
|
267
|
-
system_prompt=tool_system_prompt,
|
268
|
-
):
|
269
|
-
complete_text_response += processed_chunk
|
270
|
-
yield processed_chunk
|
271
|
-
else:
|
272
|
-
# For audio output, collect the full tool response first
|
273
|
-
tool_response = ""
|
274
|
-
async for processed_chunk in self.llm_provider.generate_text(
|
275
|
-
prompt=user_prompt,
|
276
|
-
system_prompt=tool_system_prompt,
|
277
|
-
):
|
278
|
-
tool_response += processed_chunk
|
279
|
-
|
280
|
-
# Add to our complete text record and full audio buffer
|
281
|
-
tool_response = self._clean_for_audio(
|
282
|
-
tool_response)
|
283
|
-
complete_text_response += tool_response
|
284
|
-
full_response_buffer += tool_response
|
285
|
-
else:
|
286
|
-
# For non-tool JSON, still capture the text
|
287
|
-
complete_text_response += json_buffer
|
288
|
-
|
289
|
-
if output_format == "text":
|
290
|
-
yield json_buffer
|
291
|
-
else:
|
292
|
-
# Add to full response buffer for audio
|
293
|
-
full_response_buffer += json_buffer
|
294
|
-
|
295
|
-
# Reset JSON handling
|
296
|
-
is_json = False
|
297
|
-
json_buffer = ""
|
298
|
-
|
299
|
-
except json.JSONDecodeError:
|
300
|
-
# JSON not complete yet, continue collecting
|
301
|
-
pass
|
302
|
-
else:
|
303
|
-
# For regular text
|
304
|
-
complete_text_response += chunk
|
305
|
-
|
306
|
-
if output_format == "text":
|
307
|
-
# For text output, yield directly
|
308
|
-
yield chunk
|
309
|
-
else:
|
310
|
-
# For audio output, add to the full response buffer
|
311
|
-
full_response_buffer += chunk
|
312
|
-
|
313
|
-
# Handle any leftover JSON buffer
|
314
|
-
if json_buffer:
|
315
|
-
complete_text_response += json_buffer
|
329
|
+
# STEP 3: Check for possible partial start markers at the end of the chunk
|
330
|
+
# This helps detect markers split across chunks
|
331
|
+
potential_marker = False
|
332
|
+
for i in range(1, len(start_marker)):
|
333
|
+
if combined_chunk.endswith(start_marker[:i]):
|
334
|
+
# Found a partial marker at the end
|
335
|
+
# Save the partial marker
|
336
|
+
pending_chunk = combined_chunk[-i:]
|
337
|
+
# Everything except the partial marker
|
338
|
+
chunk_to_yield = combined_chunk[:-i]
|
339
|
+
potential_marker = True
|
340
|
+
print(
|
341
|
+
f"Potential partial marker detected: '{pending_chunk}'")
|
342
|
+
break
|
343
|
+
|
344
|
+
if potential_marker:
|
345
|
+
# Process the safe part of the chunk
|
346
|
+
if chunk_to_yield and output_format == "text":
|
347
|
+
yield chunk_to_yield
|
348
|
+
if chunk_to_yield:
|
349
|
+
complete_text_response += chunk_to_yield
|
350
|
+
if output_format == "audio":
|
351
|
+
full_response_buffer += chunk_to_yield
|
352
|
+
continue
|
353
|
+
|
354
|
+
# STEP 4: Normal text processing for non-tool call content
|
316
355
|
if output_format == "text":
|
317
|
-
yield
|
318
|
-
else:
|
319
|
-
full_response_buffer += json_buffer
|
356
|
+
yield combined_chunk
|
320
357
|
|
321
|
-
|
358
|
+
complete_text_response += combined_chunk
|
359
|
+
if output_format == "audio":
|
360
|
+
full_response_buffer += combined_chunk
|
361
|
+
|
362
|
+
# Process any incomplete tool call as regular text
|
363
|
+
if is_tool_call and tool_buffer:
|
364
|
+
print(
|
365
|
+
f"Incomplete tool call detected, returning as regular text: {len(tool_buffer)} chars")
|
366
|
+
if output_format == "text":
|
367
|
+
yield tool_buffer
|
368
|
+
|
369
|
+
complete_text_response += tool_buffer
|
370
|
+
if output_format == "audio":
|
371
|
+
full_response_buffer += tool_buffer
|
372
|
+
|
373
|
+
# For audio output, generate speech from the complete buffer
|
322
374
|
if output_format == "audio" and full_response_buffer:
|
323
375
|
# Clean text before TTS
|
376
|
+
print(
|
377
|
+
f"Processing {len(full_response_buffer)} characters for audio output")
|
324
378
|
full_response_buffer = self._clean_for_audio(
|
325
379
|
full_response_buffer)
|
326
380
|
|
@@ -335,9 +389,15 @@ class AgentService(AgentServiceInterface):
|
|
335
389
|
|
336
390
|
# Store the complete text response
|
337
391
|
self.last_text_response = complete_text_response
|
392
|
+
print(
|
393
|
+
f"Response generation complete: {len(complete_text_response)} chars")
|
338
394
|
|
339
395
|
except Exception as e:
|
340
396
|
error_msg = f"I apologize, but I encountered an error: {str(e)}"
|
397
|
+
print(f"Error in generate_response: {str(e)}")
|
398
|
+
import traceback
|
399
|
+
print(traceback.format_exc())
|
400
|
+
|
341
401
|
if output_format == "audio":
|
342
402
|
async for chunk in self.llm_provider.tts(
|
343
403
|
error_msg,
|
@@ -349,52 +409,73 @@ class AgentService(AgentServiceInterface):
|
|
349
409
|
else:
|
350
410
|
yield error_msg
|
351
411
|
|
352
|
-
|
353
|
-
|
354
|
-
print(traceback.format_exc())
|
412
|
+
async def _bytes_to_generator(self, data: bytes) -> AsyncGenerator[bytes, None]:
|
413
|
+
"""Convert bytes to an async generator for streaming.
|
355
414
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
"""
|
415
|
+
Args:
|
416
|
+
data: Bytes of audio data
|
417
|
+
|
418
|
+
Yields:
|
419
|
+
Chunks of audio data
|
420
|
+
"""
|
421
|
+
# Define a reasonable chunk size (adjust based on your needs)
|
422
|
+
chunk_size = 4096
|
423
|
+
|
424
|
+
for i in range(0, len(data), chunk_size):
|
425
|
+
yield data[i:i + chunk_size]
|
426
|
+
# Small delay to simulate streaming
|
427
|
+
await asyncio.sleep(0.01)
|
428
|
+
|
429
|
+
async def _handle_tool_call(self, agent_name: str, tool_text: str) -> str:
|
430
|
+
"""Handle marker-based tool calls."""
|
362
431
|
try:
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
432
|
+
# Extract the content between markers
|
433
|
+
start_marker = "[TOOL]"
|
434
|
+
end_marker = "[/TOOL]"
|
435
|
+
|
436
|
+
start_idx = tool_text.find(start_marker) + len(start_marker)
|
437
|
+
end_idx = tool_text.find(end_marker)
|
438
|
+
|
439
|
+
tool_content = tool_text[start_idx:end_idx].strip()
|
440
|
+
|
441
|
+
# Parse the lines to extract name and parameters
|
442
|
+
tool_name = None
|
443
|
+
parameters = {}
|
444
|
+
|
445
|
+
for line in tool_content.split("\n"):
|
446
|
+
line = line.strip()
|
447
|
+
if not line:
|
448
|
+
continue
|
449
|
+
|
450
|
+
if line.startswith("name:"):
|
451
|
+
tool_name = line[5:].strip()
|
452
|
+
elif line.startswith("parameters:"):
|
453
|
+
params_text = line[11:].strip()
|
454
|
+
# Parse comma-separated parameters
|
455
|
+
param_pairs = params_text.split(",")
|
456
|
+
for pair in param_pairs:
|
457
|
+
if "=" in pair:
|
458
|
+
k, v = pair.split("=", 1)
|
459
|
+
parameters[k.strip()] = v.strip()
|
460
|
+
|
461
|
+
# Execute the tool
|
462
|
+
result = await self.execute_tool(agent_name, tool_name, parameters)
|
463
|
+
|
464
|
+
# Return the result as string
|
465
|
+
if result.get("status") == "success":
|
466
|
+
tool_result = str(result.get("result", ""))
|
467
|
+
return tool_result
|
382
468
|
else:
|
383
|
-
|
469
|
+
error_msg = f"Error calling {tool_name}: {result.get('message', 'Unknown error')}"
|
470
|
+
return error_msg
|
384
471
|
|
385
|
-
# If we get here, it wasn't properly handled as a tool
|
386
|
-
return f"The following request was not processed as a valid tool call:\n{json_chunk}"
|
387
|
-
except json.JSONDecodeError as e:
|
388
|
-
print(f"JSON decode error in tool call: {e}")
|
389
|
-
return json_chunk
|
390
472
|
except Exception as e:
|
391
|
-
print(f"Unexpected error in tool call handling: {str(e)}")
|
392
473
|
import traceback
|
393
474
|
print(traceback.format_exc())
|
394
475
|
return f"Error processing tool call: {str(e)}"
|
395
476
|
|
396
477
|
def _get_tool_usage_prompt(self, agent_name: str) -> str:
|
397
|
-
"""Generate
|
478
|
+
"""Generate marker-based instructions for tool usage."""
|
398
479
|
# Get tools assigned to this agent
|
399
480
|
tools = self.get_agent_tools(agent_name)
|
400
481
|
if not tools:
|
@@ -405,29 +486,38 @@ class AgentService(AgentServiceInterface):
|
|
405
486
|
tools_json = json.dumps(tools, indent=2)
|
406
487
|
|
407
488
|
return f"""
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
489
|
+
AVAILABLE TOOLS:
|
490
|
+
{tools_json}
|
491
|
+
|
492
|
+
⚠️ CRITICAL INSTRUCTION: When using a tool, NEVER include explanatory text.
|
493
|
+
Only output the exact tool call format shown below with NO other text.
|
494
|
+
|
495
|
+
TOOL USAGE FORMAT:
|
496
|
+
[TOOL]
|
497
|
+
name: tool_name
|
498
|
+
parameters: key1=value1, key2=value2
|
499
|
+
[/TOOL]
|
500
|
+
|
501
|
+
EXAMPLES:
|
502
|
+
|
503
|
+
✅ CORRECT - ONLY the tool call with NOTHING else:
|
504
|
+
[TOOL]
|
505
|
+
name: search_internet
|
506
|
+
parameters: query=latest news on Solana
|
507
|
+
[/TOOL]
|
508
|
+
|
509
|
+
❌ INCORRECT - Never add explanatory text like this:
|
510
|
+
To get the latest news on Solana, I will search the internet.
|
511
|
+
[TOOL]
|
512
|
+
name: search_internet
|
513
|
+
parameters: query=latest news on Solana
|
514
|
+
[/TOOL]
|
515
|
+
|
516
|
+
REMEMBER:
|
517
|
+
1. Output ONLY the exact tool call format with NO additional text
|
518
|
+
2. After seeing your tool call, I will execute it automatically
|
519
|
+
3. You will receive the tool results and can then respond to the user
|
520
|
+
"""
|
431
521
|
|
432
522
|
def _clean_for_audio(self, text: str) -> str:
|
433
523
|
"""Remove Markdown formatting, emojis, and non-pronounceable characters from text.
|
@@ -502,3 +592,18 @@ class AgentService(AgentServiceInterface):
|
|
502
592
|
text = re.sub(r'\s+', ' ', text)
|
503
593
|
|
504
594
|
return text.strip()
|
595
|
+
|
596
|
+
def _clean_tool_response(self, text: str) -> str:
|
597
|
+
"""Remove any tool markers or formatting that might have leaked into the response."""
|
598
|
+
if not text:
|
599
|
+
return ""
|
600
|
+
|
601
|
+
# Remove any tool markers that might be in the response
|
602
|
+
text = text.replace("[TOOL]", "")
|
603
|
+
text = text.replace("[/TOOL]", "")
|
604
|
+
|
605
|
+
# Remove the word TOOL from start if it appears
|
606
|
+
if text.lstrip().startswith("TOOL"):
|
607
|
+
text = text.lstrip().replace("TOOL", "", 1)
|
608
|
+
|
609
|
+
return text.strip()
|
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-24.0.0 → solana_agent-24.1.1}/solana_agent/interfaces/providers/data_storage.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
|