solana-agent 0.0.1__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.
@@ -0,0 +1 @@
1
+ from .ai import *
solana_agent/ai.py ADDED
@@ -0,0 +1,588 @@
1
+ import asyncio
2
+ from datetime import datetime
3
+ import json
4
+ from typing import AsyncGenerator, List, Literal, Optional, Dict, Any, Callable
5
+ from pydantic import BaseModel
6
+ from motor.motor_asyncio import AsyncIOMotorClient
7
+ from openai import OpenAI
8
+ import openai
9
+ import aiosqlite
10
+ from openai import AssistantEventHandler
11
+ from openai.types.beta.threads import TextDelta, Text
12
+ from typing_extensions import override
13
+ import sqlite3
14
+ import inspect
15
+ import requests
16
+ from zep_python.client import AsyncZep
17
+ from zep_python.client import Zep
18
+ from zep_python.types import Message, RoleType
19
+
20
+
21
+ def adapt_datetime(ts):
22
+ return ts.isoformat()
23
+
24
+
25
+ # Custom converter for datetime
26
+ def convert_datetime(ts):
27
+ return datetime.fromisoformat(ts)
28
+
29
+
30
+ # Register the adapter and converter
31
+ sqlite3.register_adapter(datetime, adapt_datetime)
32
+ sqlite3.register_converter("timestamp", convert_datetime)
33
+
34
+
35
+ class EventHandler(AssistantEventHandler):
36
+ def __init__(self, tool_handlers, ai_instance):
37
+ super().__init__()
38
+ self.tool_handlers = tool_handlers
39
+ self.ai_instance = ai_instance
40
+
41
+ @override
42
+ def on_text_delta(self, delta: TextDelta, snapshot: Text):
43
+ asyncio.create_task(
44
+ self.ai_instance.accumulated_value_queue.put(delta.value))
45
+
46
+ @override
47
+ def on_event(self, event):
48
+ if event.event == "thread.run.requires_action":
49
+ run_id = event.data.id
50
+ self.ai_instance.handle_requires_action(event.data, run_id)
51
+
52
+
53
+ class ToolConfig(BaseModel):
54
+ name: str
55
+ description: str
56
+ parameters: Dict[str, Any]
57
+
58
+
59
+ class MongoDatabase:
60
+ def __init__(self, db_url: str, db_name: str):
61
+ self.client = AsyncIOMotorClient(db_url)
62
+ self.db = self.client[db_name]
63
+ self.threads = self.db["threads"]
64
+ self.messages = self.db["messages"]
65
+
66
+ async def save_thread_id(self, user_id: str, thread_id: str):
67
+ await self.threads.insert_one({"thread_id": thread_id, "user_id": user_id})
68
+
69
+ async def get_thread_id(self, user_id: str) -> Optional[str]:
70
+ document = await self.threads.find_one({"user_id": user_id})
71
+ return document["thread_id"] if document else None
72
+
73
+ async def save_message(self, user_id: str, metadata: Dict[str, Any]):
74
+ metadata["user_id"] = user_id
75
+ await self.messages.insert_one(metadata)
76
+
77
+ async def delete_thread_id(self, user_id: str):
78
+ document = await self.threads.find_one({"user_id": user_id})
79
+ thread_id = document["thread_id"]
80
+ openai.beta.threads.delete(thread_id)
81
+ await self.messages.delete_many({"user_id": user_id})
82
+ await self.threads.delete_one({"user_id": user_id})
83
+
84
+ async def delete_all_threads(self):
85
+ await self.threads.delete_many({})
86
+ await self.messages.delete_many({})
87
+
88
+
89
+ class SQLiteDatabase:
90
+ def __init__(self, db_path: str):
91
+ self.db_path = db_path
92
+ self.conn = sqlite3.connect(db_path)
93
+ self.conn.execute(
94
+ "CREATE TABLE IF NOT EXISTS threads (user_id TEXT, thread_id TEXT)"
95
+ )
96
+ self.conn.execute(
97
+ "CREATE TABLE IF NOT EXISTS messages (user_id TEXT, message TEXT, response TEXT, timestamp TEXT)"
98
+ )
99
+ self.conn.commit()
100
+ self.conn.close()
101
+
102
+ async def save_thread_id(self, user_id: str, thread_id: str):
103
+ async with aiosqlite.connect(
104
+ self.db_path, detect_types=sqlite3.PARSE_DECLTYPES
105
+ ) as db:
106
+ await db.execute(
107
+ "INSERT INTO threads (user_id, thread_id) VALUES (?, ?)",
108
+ (user_id, thread_id),
109
+ )
110
+ await db.commit()
111
+
112
+ async def get_thread_id(self, user_id: str) -> Optional[str]:
113
+ async with aiosqlite.connect(
114
+ self.db_path, detect_types=sqlite3.PARSE_DECLTYPES
115
+ ) as db:
116
+ async with db.execute(
117
+ "SELECT thread_id FROM threads WHERE user_id = ?", (user_id,)
118
+ ) as cursor:
119
+ row = await cursor.fetchone()
120
+ return row[0] if row else None
121
+
122
+ async def save_message(self, user_id: str, metadata: Dict[str, Any]):
123
+ async with aiosqlite.connect(
124
+ self.db_path, detect_types=sqlite3.PARSE_DECLTYPES
125
+ ) as db:
126
+ await db.execute(
127
+ "INSERT INTO messages (user_id, message, response, timestamp) VALUES (?, ?, ?, ?)",
128
+ (
129
+ user_id,
130
+ metadata["message"],
131
+ metadata["response"],
132
+ metadata["timestamp"],
133
+ ),
134
+ )
135
+ await db.commit()
136
+
137
+ async def delete_thread_id(self, user_id: str):
138
+ async with aiosqlite.connect(
139
+ self.db_path, detect_types=sqlite3.PARSE_DECLTYPES
140
+ ) as db:
141
+ async with db.execute(
142
+ "SELECT thread_id FROM threads WHERE user_id = ?", (user_id,)
143
+ ) as cursor:
144
+ row = await cursor.fetchone()
145
+ if row:
146
+ thread_id = row[0]
147
+ openai.beta.threads.delete(thread_id)
148
+ await db.execute(
149
+ "DELETE FROM messages WHERE user_id = ?", (user_id,)
150
+ )
151
+ await db.execute(
152
+ "DELETE FROM threads WHERE user_id = ?", (user_id,)
153
+ )
154
+ await db.commit()
155
+
156
+ async def delete_all_threads(self):
157
+ async with aiosqlite.connect(
158
+ self.db_path, detect_types=sqlite3.PARSE_DECLTYPES
159
+ ) as db:
160
+ await db.execute("DELETE FROM messages")
161
+ await db.execute("DELETE FROM threads")
162
+ await db.commit()
163
+
164
+
165
+ class AI:
166
+ def __init__(
167
+ self,
168
+ openai_api_key: str,
169
+ name: str,
170
+ instructions: str,
171
+ database: Any,
172
+ zep_api_key: str = None,
173
+ zep_base_url: str = None,
174
+ perplexity_api_key: str = None,
175
+ grok_api_key: str = None,
176
+ code_interpreter: bool = True,
177
+ model: Literal["gpt-4o-mini", "gpt-4o"] = "gpt-4o-mini",
178
+ ):
179
+ self.client = OpenAI(api_key=openai_api_key)
180
+ self.name = name
181
+ self.instructions = instructions
182
+ self.model = model
183
+ self.tools = [{"type": "code_interpreter"}] if code_interpreter else []
184
+ self.tool_handlers = {}
185
+ self.assistant_id = None
186
+ self.database = database
187
+ self.accumulated_value_queue = asyncio.Queue()
188
+ self.zep = (
189
+ AsyncZep(api_key=zep_api_key, base_url=zep_base_url)
190
+ if zep_api_key
191
+ else None
192
+ )
193
+ self.sync_zep = (
194
+ Zep(api_key=zep_api_key, base_url=zep_base_url) if zep_api_key else None
195
+ )
196
+ self.perplexity_api_key = perplexity_api_key
197
+ self.grok_api_key = grok_api_key
198
+
199
+ async def __aenter__(self):
200
+ assistants = openai.beta.assistants.list()
201
+ existing_assistant = next(
202
+ (a for a in assistants if a.name == self.name), None)
203
+
204
+ if existing_assistant:
205
+ self.assistant_id = existing_assistant.id
206
+ else:
207
+ self.assistant_id = openai.beta.assistants.create(
208
+ name=self.name,
209
+ instructions=self.instructions,
210
+ tools=self.tools,
211
+ model=self.model,
212
+ ).id
213
+ await self.database.delete_all_threads()
214
+
215
+ return self
216
+
217
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
218
+ # Perform any cleanup actions here
219
+ pass
220
+
221
+ async def create_thread(self, user_id: str) -> str:
222
+ thread_id = await self.database.get_thread_id(user_id)
223
+
224
+ if thread_id is None:
225
+ thread = openai.beta.threads.create()
226
+ thread_id = thread.id
227
+ await self.database.save_thread_id(user_id, thread_id)
228
+ if self.zep:
229
+ await self.zep.user.add(user_id=user_id)
230
+ await self.zep.memory.add_session(user_id=user_id, session_id=user_id)
231
+
232
+ return thread_id
233
+
234
+ async def cancel_run(self, thread_id: str, run_id: str):
235
+ try:
236
+ self.client.beta.threads.runs.cancel(
237
+ thread_id=thread_id, run_id=run_id)
238
+ except Exception as e:
239
+ print(f"Error cancelling run: {e}")
240
+
241
+ async def get_active_run(self, thread_id: str) -> Optional[str]:
242
+ runs = self.client.beta.threads.runs.list(thread_id=thread_id, limit=1)
243
+ for run in runs:
244
+ if run.status in ["in_progress"]:
245
+ return run.id
246
+ return None
247
+
248
+ async def get_run_status(self, thread_id: str, run_id: str) -> str:
249
+ run = self.client.beta.threads.runs.retrieve(
250
+ thread_id=thread_id, run_id=run_id)
251
+ return run.status
252
+
253
+ # search facts tool - has to be sync
254
+ def search_facts(
255
+ self,
256
+ user_id: str,
257
+ query: str,
258
+ limit: int | None = None,
259
+ ) -> List[str] | None:
260
+ if self.sync_zep:
261
+ facts = []
262
+ results = self.sync_zep.memory.search_sessions(
263
+ user_id=user_id,
264
+ text=query,
265
+ limit=limit,
266
+ )
267
+ for result in results.results:
268
+ fact = result.fact.fact
269
+ if fact:
270
+ facts.append(fact)
271
+ return facts
272
+ return None
273
+
274
+ # search internet tool - has to be sync
275
+ def search_internet(
276
+ self,
277
+ query: str,
278
+ model: Literal[
279
+ "sonar", "sonar-pro", "sonar-reasoning-pro", "sonar-reasoning"
280
+ ] = "sonar",
281
+ ) -> str:
282
+ try:
283
+ url = "https://api.perplexity.ai/chat/completions"
284
+
285
+ payload = {
286
+ "model": model,
287
+ "messages": [
288
+ {
289
+ "role": "system",
290
+ "content": "You answer the user's query.",
291
+ },
292
+ {
293
+ "role": "user",
294
+ "content": query,
295
+ },
296
+ ],
297
+ }
298
+ headers = {
299
+ "Authorization": f"Bearer {self.perplexity_api_key}",
300
+ "Content-Type": "application/json",
301
+ }
302
+
303
+ response = requests.post(url, json=payload, headers=headers)
304
+ if response.status_code == 200:
305
+ data = response.json()
306
+ content = data["choices"][0]["message"]["content"]
307
+ return content
308
+ else:
309
+ return (
310
+ f"Failed to search Perplexity. Status code: {response.status_code}"
311
+ )
312
+ except Exception as e:
313
+ return f"Failed to search Perplexity. Error: {e}"
314
+
315
+ # reason tool - has to be sync
316
+ def reason(
317
+ self,
318
+ user_id: str,
319
+ query: str,
320
+ perplexity_model: Literal[
321
+ "sonar", "sonar-pro", "sonar-reasoning-pro", "sonar-reasoning"
322
+ ] = "sonar",
323
+ openai_model: Literal["o1", "o3-mini"] = "o3-mini",
324
+ grok_model: Literal["grok-beta"] = "grok-beta",
325
+ ) -> str:
326
+ try:
327
+ facts = self.search_facts(user_id, query)
328
+ if not facts:
329
+ facts = ""
330
+ search_results = self.search_internet(query, perplexity_model)
331
+ x_search_results = self.search_x(query, grok_model)
332
+
333
+ response = self.client.chat.completions.create(
334
+ model=openai_model,
335
+ messages=[
336
+ {
337
+ "role": "system",
338
+ "content": "You combine the data with your reasoning to answer the query.",
339
+ },
340
+ {
341
+ "role": "user",
342
+ "content": f"Query: {query}, Facts: {facts}, Internet Search Results: {search_results}, X Search Results: {x_search_results}",
343
+ },
344
+ ],
345
+ )
346
+ return response.choices[0].message.content
347
+ except Exception as e:
348
+ return f"Failed to reason. Error: {e}"
349
+
350
+ # x search tool - has to be sync
351
+ def search_x(self, query: str, model: Literal["grok-beta"] = "grok-beta") -> str:
352
+ try:
353
+ client = OpenAI(api_key=self.grok_api_key,
354
+ base_url="https://api.x.ai/v1")
355
+
356
+ completion = client.chat.completions.create(
357
+ model=model,
358
+ messages=[
359
+ {
360
+ "role": "system",
361
+ "content": "You answer the user's query.",
362
+ },
363
+ {"role": "user", "content": query},
364
+ ],
365
+ )
366
+
367
+ return completion.choices[0].message.content
368
+ except Exception as e:
369
+ return f"Failed to search X. Error: {e}"
370
+
371
+ async def delete_facts(self, user_id: str):
372
+ if self.zep:
373
+ await self.zep.memory.delete(session_id=user_id)
374
+
375
+ async def listen(self, audio_content: bytes, input_format: str) -> str:
376
+ transcription = self.client.audio.transcriptions.create(
377
+ model="whisper-1",
378
+ file=(f"file.{input_format}", audio_content),
379
+ )
380
+ return transcription.text
381
+
382
+ async def text(self, user_id: str, user_text: str) -> AsyncGenerator[str, None]:
383
+ self.accumulated_value_queue = asyncio.Queue()
384
+
385
+ thread_id = await self.database.get_thread_id(user_id)
386
+
387
+ if thread_id is None:
388
+ thread_id = await self.create_thread(user_id)
389
+
390
+ self.current_thread_id = thread_id
391
+
392
+ # Check for active runs and cancel if necessary
393
+ active_run_id = await self.get_active_run(thread_id)
394
+ if active_run_id:
395
+ await self.cancel_run(thread_id, active_run_id)
396
+ while await self.get_run_status(thread_id, active_run_id) != "cancelled":
397
+ await asyncio.sleep(0.1)
398
+
399
+ # Create a message in the thread
400
+ self.client.beta.threads.messages.create(
401
+ thread_id=thread_id,
402
+ role="user",
403
+ content=user_text,
404
+ )
405
+ event_handler = EventHandler(self.tool_handlers, self)
406
+
407
+ async def stream_processor():
408
+ with self.client.beta.threads.runs.stream(
409
+ thread_id=thread_id,
410
+ assistant_id=self.assistant_id,
411
+ event_handler=event_handler,
412
+ ) as stream:
413
+ stream.until_done()
414
+
415
+ # Start the stream processor in a separate task
416
+ asyncio.create_task(stream_processor())
417
+
418
+ # Yield values from the queue as they become available
419
+ full_response = ""
420
+ while True:
421
+ try:
422
+ value = await asyncio.wait_for(
423
+ self.accumulated_value_queue.get(), timeout=0.1
424
+ )
425
+ if value is not None:
426
+ full_response += value
427
+ yield value
428
+ except asyncio.TimeoutError:
429
+ if self.accumulated_value_queue.empty():
430
+ break
431
+
432
+ # Save the message to the database
433
+ metadata = {
434
+ "user_id": user_id,
435
+ "message": user_text,
436
+ "response": full_response,
437
+ "timestamp": datetime.now(),
438
+ }
439
+
440
+ await self.database.save_message(user_id, metadata)
441
+ if self.zep:
442
+ messages = [
443
+ Message(
444
+ role="user",
445
+ role_type=RoleType["user"],
446
+ content=user_text,
447
+ ),
448
+ Message(
449
+ role="assistant",
450
+ role_type=RoleType["assistant"],
451
+ content=full_response,
452
+ ),
453
+ ]
454
+ await self.zep.memory.add(
455
+ user_id=user_id, session_id=user_id, messages=messages
456
+ )
457
+
458
+ async def conversation(
459
+ self,
460
+ user_id: str,
461
+ audio_bytes: bytes,
462
+ voice: Literal["alloy", "echo", "fable",
463
+ "onyx", "nova", "shimmer"] = "nova",
464
+ input_format: Literal[
465
+ "flac", "m4a", "mp3", "mp4", "mpeg", "mpga", "oga", "ogg", "wav", "webm"
466
+ ] = "mp4",
467
+ response_format: Literal["mp3", "opus",
468
+ "aac", "flac", "wav", "pcm"] = "aac",
469
+ ) -> AsyncGenerator[bytes, None]:
470
+ # Reset the queue for each new conversation
471
+ self.accumulated_value_queue = asyncio.Queue()
472
+
473
+ thread_id = await self.database.get_thread_id(user_id)
474
+
475
+ if thread_id is None:
476
+ thread_id = await self.create_thread(user_id)
477
+
478
+ self.current_thread_id = thread_id
479
+ transcript = await self.listen(audio_bytes, input_format)
480
+ event_handler = EventHandler(self.tool_handlers, self)
481
+ openai.beta.threads.messages.create(
482
+ thread_id=thread_id,
483
+ role="user",
484
+ content=transcript,
485
+ )
486
+
487
+ async def stream_processor():
488
+ with openai.beta.threads.runs.stream(
489
+ thread_id=thread_id,
490
+ assistant_id=self.assistant_id,
491
+ event_handler=event_handler,
492
+ ) as stream:
493
+ stream.until_done()
494
+
495
+ # Start the stream processor in a separate task
496
+ asyncio.create_task(stream_processor())
497
+
498
+ # Collect the full response
499
+ full_response = ""
500
+ while True:
501
+ try:
502
+ value = await asyncio.wait_for(
503
+ self.accumulated_value_queue.get(), timeout=0.1
504
+ )
505
+ if value is not None:
506
+ full_response += value
507
+ except asyncio.TimeoutError:
508
+ if self.accumulated_value_queue.empty():
509
+ break
510
+
511
+ metadata = {
512
+ "user_id": user_id,
513
+ "message": transcript,
514
+ "response": full_response,
515
+ "timestamp": datetime.now(),
516
+ }
517
+
518
+ await self.database.save_message(user_id, metadata)
519
+
520
+ if self.zep:
521
+ messages = [
522
+ Message(
523
+ role="user",
524
+ role_type=RoleType["user"],
525
+ content=transcript,
526
+ ),
527
+ Message(
528
+ role="assistant",
529
+ role_type=RoleType["assistant"],
530
+ content=full_response,
531
+ ),
532
+ ]
533
+ await self.zep.memory.add(
534
+ user_id=user_id, session_id=user_id, messages=messages
535
+ )
536
+
537
+ # Generate and stream the audio response
538
+ with self.client.audio.speech.with_streaming_response.create(
539
+ model="tts-1",
540
+ voice=voice,
541
+ input=full_response,
542
+ response_format=response_format,
543
+ ) as response:
544
+ for chunk in response.iter_bytes(1024):
545
+ yield chunk
546
+
547
+ def handle_requires_action(self, data, run_id):
548
+ tool_outputs = []
549
+
550
+ for tool in data.required_action.submit_tool_outputs.tool_calls:
551
+ if tool.function.name in self.tool_handlers:
552
+ handler = self.tool_handlers[tool.function.name]
553
+ inputs = json.loads(tool.function.arguments)
554
+ output = handler(**inputs)
555
+ tool_outputs.append(
556
+ {"tool_call_id": tool.id, "output": output})
557
+
558
+ self.submit_tool_outputs(tool_outputs, run_id)
559
+
560
+ def submit_tool_outputs(self, tool_outputs, run_id):
561
+ with self.client.beta.threads.runs.submit_tool_outputs_stream(
562
+ thread_id=self.current_thread_id, run_id=run_id, tool_outputs=tool_outputs
563
+ ) as stream:
564
+ for text in stream.text_deltas:
565
+ asyncio.create_task(self.accumulated_value_queue.put(text))
566
+
567
+ def add_tool(self, func: Callable):
568
+ sig = inspect.signature(func)
569
+ parameters = {"type": "object", "properties": {}, "required": []}
570
+ for name, param in sig.parameters.items():
571
+ parameters["properties"][name] = {
572
+ "type": "string", "description": "foo"}
573
+ if param.default == inspect.Parameter.empty:
574
+ parameters["required"].append(name)
575
+ tool_config = {
576
+ "type": "function",
577
+ "function": {
578
+ "name": func.__name__,
579
+ "description": func.__doc__ or "",
580
+ "parameters": parameters,
581
+ },
582
+ }
583
+ self.tools.append(tool_config)
584
+ self.tool_handlers[func.__name__] = func
585
+ return func
586
+
587
+
588
+ tool = AI.add_tool
@@ -0,0 +1,7 @@
1
+ Copyright 2024 Bevan Hunt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.3
2
+ Name: solana-agent
3
+ Version: 0.0.1
4
+ Summary: The Best AI Agent Framework
5
+ License: MIT
6
+ Keywords: ai,openai,ai agents
7
+ Author: Bevan Hunt
8
+ Author-email: bevan@bevanhunt.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Dist: aiosqlite (>=0.21.0,<0.22.0)
20
+ Requires-Dist: motor (>=3.7.0,<4.0.0)
21
+ Requires-Dist: openai (>=1.61.1,<2.0.0)
22
+ Requires-Dist: pydantic (>=2.10.6,<3.0.0)
23
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
24
+ Requires-Dist: zep-python (>=2.0.2,<3.0.0)
25
+ Project-URL: Repository, https://github.com/truemagic-coder/solana-agent
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Solana-Agent
29
+
30
+ [![PyPI - Version](https://img.shields.io/pypi/v/solana-agent)](https://pypi.org/project/solana-agent/)
31
+
32
+ ![Solana Agent Logo](https://dl.walletbubbles.com/solana-agent-logo.png?width=200)
33
+
34
+ Solana Agent is the best AI Agent framework.
35
+
36
+ ## Features
37
+
38
+ - Streaming text-based conversations with AI
39
+ - Audio transcription and streaming text-to-speech conversion
40
+ - Thread management for maintaining conversation context
41
+ - Message persistence using SQLite or MongoDB
42
+ - Custom tool integration for extending AI capabilities
43
+ - The best memory context currently available for AI Agents
44
+ - Zep integration for tracking facts
45
+ - Search Internet with Perplexity tool
46
+ - Search Zep facts tool
47
+ - Search X with Grok tool
48
+ - Reasoning tool that combines OpenAI model reasoning, Zep facts, Internet search, and X search.
49
+ - Solana tools upcoming...
50
+
51
+ ## Installation
52
+
53
+ You can install Solana Agent using pip:
54
+
55
+ ```bash
56
+ pip install solana-agent
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ Here's a basic example of how to use Solana Agent:
62
+
63
+ ```python
64
+ from solana-agent import AI, SQLiteDatabase
65
+
66
+ async def main():
67
+ database = SQLiteDatabase("conversations.db")
68
+ async with AI("your_openai_api_key", "AI Assistant", "Your instructions here", database) as ai:
69
+ user_id = "user123"
70
+ response = await ai.text(user_id, "Hello, AI!")
71
+ async for chunk in response:
72
+ print(chunk, end="", flush=True)
73
+ print()
74
+
75
+ # Run the async main function
76
+ import asyncio
77
+ asyncio.run(main())
78
+ ```
79
+
80
+ ## Contributing
81
+
82
+ Contributions to Solana Agent are welcome! Please feel free to submit a Pull Request.
83
+
84
+ ## License
85
+
86
+ This project is licensed under the MIT License - see the LICENSE file for details.
87
+
@@ -0,0 +1,6 @@
1
+ solana_agent/__init__.py,sha256=zpfnWqANd3OHGWm7NCF5Y6m01BWG4NkNk8SK9Ex48nA,18
2
+ solana_agent/ai.py,sha256=JHpIAGMKy4WxTs2ofA7MQXn-y82F35rAa_Bb71FGzBo,20498
3
+ solana_agent-0.0.1.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
4
+ solana_agent-0.0.1.dist-info/METADATA,sha256=IwMIG1WTDxqDVESVue4HHXe14c6wIh7mHvs1DiYs-ak,2735
5
+ solana_agent-0.0.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
6
+ solana_agent-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.0.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any