solana-agent 28.1.1__py3-none-any.whl → 28.2.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/cli.py ADDED
@@ -0,0 +1,128 @@
1
+ from typing import Optional
2
+ import typer
3
+ import asyncio
4
+ import logging
5
+ from typing_extensions import Annotated
6
+ from rich.console import Console
7
+ from rich.live import Live
8
+ from rich.spinner import Spinner
9
+ from rich.prompt import Prompt
10
+
11
+ from solana_agent.client.solana_agent import SolanaAgent
12
+
13
+ # --- Basic Logging Configuration ---
14
+ logging.basicConfig(level=logging.WARNING, format="%(levelname)s:%(name)s:%(message)s")
15
+ # --- End Logging Configuration ---
16
+
17
+ app = typer.Typer()
18
+ console = Console()
19
+
20
+
21
+ async def stream_agent_response(
22
+ agent: SolanaAgent,
23
+ user_id: str,
24
+ message: str,
25
+ prompt: Optional[str] = None,
26
+ ):
27
+ """Helper function to stream and display agent response."""
28
+ full_response = ""
29
+ with Live(console=console, refresh_per_second=10, transient=True) as live:
30
+ live.update(Spinner("dots", "Thinking..."))
31
+ try:
32
+ first_chunk = True
33
+ async for chunk in agent.process(
34
+ user_id=user_id,
35
+ message=message,
36
+ output_format="text",
37
+ prompt=prompt, # Pass prompt override if provided
38
+ ):
39
+ if first_chunk:
40
+ live.update("", refresh=True) # Clear spinner
41
+ first_chunk = False
42
+ full_response += chunk
43
+ live.update(full_response)
44
+
45
+ if first_chunk: # No response received
46
+ live.update("[yellow]Agent did not produce a response.[/yellow]")
47
+
48
+ except Exception as e:
49
+ # Display error within the Live context
50
+ live.update(f"[bold red]\nError during processing:[/bold red] {e}")
51
+ # Keep the error message visible after Live exits by printing it again
52
+ console.print(f"[bold red]Error during processing:[/bold red] {e}")
53
+ full_response = "" # Ensure error message isn't printed as final response
54
+
55
+ # Print the final complete response cleanly after Live context exits
56
+ if full_response:
57
+ console.print(f"[bright_blue]Agent:[/bright_blue] {full_response}")
58
+
59
+
60
+ @app.command()
61
+ def chat(
62
+ user_id: Annotated[
63
+ str, typer.Option(help="The user ID for the conversation.")
64
+ ] = "cli_user",
65
+ config: Annotated[
66
+ str, typer.Option(help="Path to the configuration JSON file.")
67
+ ] = "config.json",
68
+ prompt: Annotated[ # Allow prompt override via option
69
+ str, typer.Option(help="Optional system prompt override for the session.")
70
+ ] = None,
71
+ ):
72
+ """
73
+ Start an interactive chat session with the Solana Agent.
74
+ Type 'exit' or 'quit' to end the session.
75
+ """
76
+ try:
77
+ with console.status("[bold green]Initializing agent...", spinner="dots"):
78
+ agent = SolanaAgent(config_path=config)
79
+ console.print("[green]Agent initialized. Start chatting![/green]")
80
+ console.print("[dim]Type 'exit' or 'quit' to end.[/dim]")
81
+
82
+ except FileNotFoundError:
83
+ console.print(
84
+ f"[bold red]Error:[/bold red] Configuration file not found at '{config}'"
85
+ )
86
+ raise typer.Exit(code=1)
87
+ except ValueError as e:
88
+ console.print(f"[bold red]Error loading configuration:[/bold red] {e}")
89
+ raise typer.Exit(code=1)
90
+ except Exception as e:
91
+ console.print(
92
+ f"[bold red]An unexpected error occurred during initialization:[/bold red] {e}"
93
+ )
94
+ raise typer.Exit(code=1)
95
+
96
+ # --- Main Interaction Loop ---
97
+ while True:
98
+ try:
99
+ # Use Rich's Prompt for better input handling
100
+ user_message = Prompt.ask("[bold green]You[/bold green]")
101
+
102
+ if user_message.lower() in ["exit", "quit"]:
103
+ console.print("[yellow]Exiting chat session.[/yellow]")
104
+ break
105
+
106
+ if not user_message.strip(): # Handle empty input
107
+ continue
108
+
109
+ # Run the async streaming function for the user's message
110
+ # Pass the optional prompt override from the command line option
111
+ asyncio.run(stream_agent_response(agent, user_id, user_message, prompt))
112
+
113
+ except KeyboardInterrupt: # Allow Ctrl+C to exit gracefully
114
+ console.print(
115
+ "\n[yellow]Exiting chat session (KeyboardInterrupt).[/yellow]"
116
+ )
117
+ break
118
+ except Exception as loop_error:
119
+ # Catch errors during the input/processing loop without crashing
120
+ console.print(
121
+ f"[bold red]An error occurred in the chat loop:[/bold red] {loop_error}"
122
+ )
123
+ # Optionally add a small delay or specific error handling here
124
+ # Consider if you want to break the loop on certain errors
125
+
126
+
127
+ if __name__ == "__main__":
128
+ app()
@@ -6,6 +6,7 @@ services and components used in the system.
6
6
  """
7
7
 
8
8
  import importlib
9
+ import logging
9
10
  from typing import Dict, Any, List
10
11
 
11
12
  # Service imports
@@ -30,6 +31,9 @@ from solana_agent.adapters.mongodb_adapter import MongoDBAdapter
30
31
  from solana_agent.domains.agent import BusinessMission
31
32
  from solana_agent.plugins.manager import PluginManager
32
33
 
34
+ # Setup logger for this module
35
+ logger = logging.getLogger(__name__)
36
+
33
37
 
34
38
  class SolanaAgentFactory:
35
39
  """Factory for creating and wiring components of the Solana Agent system."""
@@ -45,7 +49,9 @@ class SolanaAgentFactory:
45
49
  class_path = config.get("class")
46
50
  guardrail_config = config.get("config", {})
47
51
  if not class_path:
48
- print(f"Guardrail config missing 'class': {config}")
52
+ logger.warning(
53
+ f"Guardrail config missing 'class': {config}"
54
+ ) # Use logger.warning
49
55
  continue
50
56
  try:
51
57
  module_path, class_name = class_path.rsplit(".", 1)
@@ -54,15 +60,23 @@ class SolanaAgentFactory:
54
60
  # Instantiate the guardrail, handling potential errors during init
55
61
  try:
56
62
  guardrails.append(guardrail_class(config=guardrail_config))
57
- print(f"Successfully loaded guardrail: {class_path}")
63
+ logger.info(
64
+ f"Successfully loaded guardrail: {class_path}"
65
+ ) # Use logger.info
58
66
  except Exception as init_e:
59
- print(f"Error initializing guardrail '{class_path}': {init_e}")
67
+ logger.error(
68
+ f"Error initializing guardrail '{class_path}': {init_e}"
69
+ ) # Use logger.error
60
70
  # Optionally re-raise or just skip this guardrail
61
71
 
62
72
  except (ImportError, AttributeError, ValueError) as e:
63
- print(f"Error loading guardrail class '{class_path}': {e}")
73
+ logger.error(
74
+ f"Error loading guardrail class '{class_path}': {e}"
75
+ ) # Use logger.error
64
76
  except Exception as e: # Catch unexpected errors during import/getattr
65
- print(f"Unexpected error loading guardrail '{class_path}': {e}")
77
+ logger.exception(
78
+ f"Unexpected error loading guardrail '{class_path}': {e}"
79
+ ) # Use logger.exception
66
80
  return guardrails
67
81
 
68
82
  @staticmethod
@@ -142,7 +156,7 @@ class SolanaAgentFactory:
142
156
  output_guardrails: List[OutputGuardrail] = (
143
157
  SolanaAgentFactory._create_guardrails(guardrail_config.get("output", []))
144
158
  )
145
- print(
159
+ logger.info( # Use logger.info
146
160
  f"Loaded {len(input_guardrails)} input guardrails and {len(output_guardrails)} output guardrails."
147
161
  )
148
162
 
@@ -161,7 +175,7 @@ class SolanaAgentFactory:
161
175
  )
162
176
 
163
177
  # Debug the agent service tool registry
164
- print(
178
+ logger.debug( # Use logger.debug
165
179
  f"Agent service tools after initialization: {agent_service.tool_registry.list_all_tools()}"
166
180
  )
167
181
 
@@ -171,9 +185,9 @@ class SolanaAgentFactory:
171
185
  )
172
186
  try:
173
187
  loaded_plugins = agent_service.plugin_manager.load_plugins()
174
- print(f"Loaded {loaded_plugins} plugins")
188
+ logger.info(f"Loaded {loaded_plugins} plugins") # Use logger.info
175
189
  except Exception as e:
176
- print(f"Error loading plugins: {e}")
190
+ logger.error(f"Error loading plugins: {e}") # Use logger.error
177
191
  loaded_plugins = 0
178
192
 
179
193
  # Register predefined agents
@@ -187,11 +201,11 @@ class SolanaAgentFactory:
187
201
  # Register tools for this agent
188
202
  if "tools" in agent_config:
189
203
  for tool_name in agent_config["tools"]:
190
- print(
204
+ logger.debug( # Use logger.debug
191
205
  f"Available tools before registering {tool_name}: {agent_service.tool_registry.list_all_tools()}"
192
206
  )
193
207
  agent_service.assign_tool_for_agent(agent_config["name"], tool_name)
194
- print(
208
+ logger.info( # Use logger.info
195
209
  f"Successfully registered {tool_name} for agent {agent_config['name']}"
196
210
  )
197
211
 
@@ -220,6 +234,11 @@ class SolanaAgentFactory:
220
234
  openai_dimensions = 3072
221
235
  elif openai_model_name == "text-embedding-3-small": # pragma: no cover
222
236
  openai_dimensions = 1536 # pragma: no cover
237
+ else: # pragma: no cover
238
+ logger.warning(
239
+ f"Unknown OpenAI embedding model '{openai_model_name}' specified for KB. Defaulting dimensions to 3072."
240
+ ) # pragma: no cover
241
+ openai_dimensions = 3072 # pragma: no cover
223
242
 
224
243
  # Create Pinecone adapter for KB
225
244
  # It now relies on external embeddings, so dimension MUST match OpenAI model
@@ -264,13 +283,13 @@ class SolanaAgentFactory:
264
283
  "breakpoint_percentile", 95
265
284
  ),
266
285
  )
267
- print("Knowledge Base Service initialized successfully.")
286
+ logger.info(
287
+ "Knowledge Base Service initialized successfully."
288
+ ) # Use logger.info
268
289
 
269
290
  except Exception as e:
270
- print(f"Failed to initialize Knowledge Base: {e}")
271
- import traceback
272
-
273
- print(traceback.format_exc())
291
+ # Use logger.exception to include traceback automatically
292
+ logger.exception(f"Failed to initialize Knowledge Base: {e}")
274
293
  knowledge_base = None # Ensure KB is None if init fails
275
294
 
276
295
  # Create and return the query service
@@ -6,6 +6,7 @@ loads, and manages plugins.
6
6
  """
7
7
 
8
8
  import importlib
9
+ import logging
9
10
  from typing import Dict, List, Any, Optional
10
11
  import importlib.metadata
11
12
 
@@ -15,6 +16,9 @@ from solana_agent.interfaces.plugins.plugins import (
15
16
  from solana_agent.interfaces.plugins.plugins import Plugin
16
17
  from solana_agent.plugins.registry import ToolRegistry
17
18
 
19
+ # Setup logger for this module
20
+ logger = logging.getLogger(__name__)
21
+
18
22
 
19
23
  class PluginManager(PluginManagerInterface):
20
24
  """Manager for discovering and loading plugins."""
@@ -50,11 +54,15 @@ class PluginManager(PluginManagerInterface):
50
54
 
51
55
  # Only store plugin if both initialize and configure succeed
52
56
  self._plugins[plugin.name] = plugin
53
- print(f"Successfully registered plugin {plugin.name}")
57
+ logger.info(
58
+ f"Successfully registered plugin {plugin.name}"
59
+ ) # Use logger.info
54
60
  return True
55
61
 
56
62
  except Exception as e:
57
- print(f"Error registering plugin {plugin.name}: {e}")
63
+ logger.error(
64
+ f"Error registering plugin {plugin.name}: {e}"
65
+ ) # Use logger.error
58
66
  # Remove plugin from registry if it was added
59
67
  self._plugins.pop(plugin.name, None)
60
68
  return False
@@ -74,11 +82,15 @@ class PluginManager(PluginManagerInterface):
74
82
  # Skip if this entry point has already been loaded
75
83
  entry_point_id = f"{entry_point.name}:{entry_point.value}"
76
84
  if entry_point_id in PluginManager._loaded_entry_points:
77
- print(f"Skipping already loaded plugin: {entry_point.name}")
85
+ logger.info(
86
+ f"Skipping already loaded plugin: {entry_point.name}"
87
+ ) # Use logger.info
78
88
  continue
79
89
 
80
90
  try:
81
- print(f"Found plugin entry point: {entry_point.name}")
91
+ logger.info(
92
+ f"Found plugin entry point: {entry_point.name}"
93
+ ) # Use logger.info
82
94
  PluginManager._loaded_entry_points.add(entry_point_id)
83
95
  plugin_factory = entry_point.load()
84
96
  plugin = plugin_factory()
@@ -89,7 +101,9 @@ class PluginManager(PluginManagerInterface):
89
101
  loaded_plugins.append(entry_point.name)
90
102
 
91
103
  except Exception as e:
92
- print(f"Error loading plugin {entry_point.name}: {e}")
104
+ logger.error(
105
+ f"Error loading plugin {entry_point.name}: {e}"
106
+ ) # Use logger.error
93
107
 
94
108
  return loaded_plugins
95
109
 
@@ -142,10 +156,12 @@ class PluginManager(PluginManagerInterface):
142
156
  """
143
157
  self.config.update(config)
144
158
  self.tool_registry.configure_all_tools(config)
145
- print("Configuring all plugins with updated config")
159
+ logger.info("Configuring all plugins with updated config") # Use logger.info
146
160
  for name, plugin in self._plugins.items():
147
161
  try:
148
- print(f"Configuring plugin: {name}")
162
+ logger.info(f"Configuring plugin: {name}") # Use logger.info
149
163
  plugin.configure(self.config)
150
164
  except Exception as e:
151
- print(f"Error configuring plugin {name}: {e}")
165
+ logger.error(
166
+ f"Error configuring plugin {name}: {e}"
167
+ ) # Use logger.error
@@ -5,6 +5,7 @@ This module implements the concrete ToolRegistry that manages tools
5
5
  and their access permissions.
6
6
  """
7
7
 
8
+ import logging # Import logging
8
9
  from typing import Dict, List, Any, Optional
9
10
 
10
11
  from solana_agent.interfaces.plugins.plugins import (
@@ -12,6 +13,9 @@ from solana_agent.interfaces.plugins.plugins import (
12
13
  )
13
14
  from solana_agent.interfaces.plugins.plugins import Tool
14
15
 
16
+ # Setup logger for this module
17
+ logger = logging.getLogger(__name__)
18
+
15
19
 
16
20
  class ToolRegistry(ToolRegistryInterface):
17
21
  """Instance-based registry that manages tools and their access permissions."""
@@ -28,10 +32,12 @@ class ToolRegistry(ToolRegistryInterface):
28
32
  tool.configure(self._config)
29
33
 
30
34
  self._tools[tool.name] = tool
31
- print(f"Successfully registered and configured tool: {tool.name}")
35
+ logger.info(
36
+ f"Successfully registered and configured tool: {tool.name}"
37
+ ) # Use logger.info
32
38
  return True
33
39
  except Exception as e:
34
- print(f"Error registering tool: {str(e)}")
40
+ logger.error(f"Error registering tool: {str(e)}") # Use logger.error
35
41
  return False
36
42
 
37
43
  def get_tool(self, tool_name: str) -> Optional[Tool]:
@@ -41,7 +47,7 @@ class ToolRegistry(ToolRegistryInterface):
41
47
  def assign_tool_to_agent(self, agent_name: str, tool_name: str) -> bool:
42
48
  """Give an agent access to a specific tool."""
43
49
  if tool_name not in self._tools:
44
- print(
50
+ logger.error( # Use logger.error
45
51
  f"Error: Tool {tool_name} is not registered. Available tools: {list(self._tools.keys())}"
46
52
  )
47
53
  return False
@@ -53,8 +59,12 @@ class ToolRegistry(ToolRegistryInterface):
53
59
  # Add new tool to existing list
54
60
  self._agent_tools[agent_name] = [*self._agent_tools[agent_name], tool_name]
55
61
 
56
- print(f"Successfully assigned tool {tool_name} to agent {agent_name}")
57
- print(f"Agent {agent_name} now has access to: {self._agent_tools[agent_name]}")
62
+ logger.info(
63
+ f"Successfully assigned tool {tool_name} to agent {agent_name}"
64
+ ) # Use logger.info
65
+ logger.info(
66
+ f"Agent {agent_name} now has access to: {self._agent_tools[agent_name]}"
67
+ ) # Use logger.info
58
68
 
59
69
  return True
60
70
 
@@ -70,7 +80,10 @@ class ToolRegistry(ToolRegistryInterface):
70
80
  for name in tool_names
71
81
  if name in self._tools
72
82
  ]
73
- print(f"Tools available to agent {agent_name}: {[t['name'] for t in tools]}")
83
+ # Changed to debug level as this might be verbose during normal operation
84
+ logger.debug(
85
+ f"Tools available to agent {agent_name}: {[t['name'] for t in tools]}"
86
+ ) # Use logger.debug
74
87
  return tools
75
88
 
76
89
  def list_all_tools(self) -> List[str]:
@@ -88,13 +101,13 @@ class ToolRegistry(ToolRegistryInterface):
88
101
 
89
102
  for name, tool in self._tools.items():
90
103
  try:
91
- print(f"Configuring tool: {name}")
104
+ logger.info(f"Configuring tool: {name}") # Use logger.info
92
105
  tool.configure(self._config)
93
106
  except Exception as e:
94
- print(f"Error configuring tool {name}: {e}")
107
+ logger.error(f"Error configuring tool {name}: {e}") # Use logger.error
95
108
  configure_errors.append((name, str(e)))
96
109
 
97
110
  if configure_errors:
98
- print("The following tools failed to configure:")
111
+ logger.error("The following tools failed to configure:") # Use logger.error
99
112
  for name, error in configure_errors:
100
- print(f"- {name}: {error}")
113
+ logger.error(f"- {name}: {error}") # Use logger.error
@@ -1,3 +1,4 @@
1
+ import logging # Import logging
1
2
  from copy import deepcopy
2
3
  from typing import List, Dict, Any, Optional, Tuple
3
4
  from datetime import datetime, timezone
@@ -6,6 +7,9 @@ from zep_cloud.types import Message
6
7
  from solana_agent.interfaces.providers.memory import MemoryProvider
7
8
  from solana_agent.adapters.mongodb_adapter import MongoDBAdapter
8
9
 
10
+ # Setup logger for this module
11
+ logger = logging.getLogger(__name__)
12
+
9
13
 
10
14
  class MemoryRepository(MemoryProvider):
11
15
  """Combined Zep and MongoDB implementation of MemoryProvider."""
@@ -30,7 +34,7 @@ class MemoryRepository(MemoryProvider):
30
34
  self.mongo.create_index(self.collection, [("user_id", 1)])
31
35
  self.mongo.create_index(self.collection, [("timestamp", 1)])
32
36
  except Exception as e:
33
- print(f"Error initializing MongoDB: {e}")
37
+ logger.error(f"Error initializing MongoDB: {e}") # Use logger.error
34
38
 
35
39
  self.zep = None
36
40
  # Initialize Zep
@@ -80,7 +84,7 @@ class MemoryRepository(MemoryProvider):
80
84
  }
81
85
  self.mongo.insert_one(self.collection, doc)
82
86
  except Exception as e:
83
- print(f"MongoDB storage error: {e}")
87
+ logger.error(f"MongoDB storage error: {e}") # Use logger.error
84
88
 
85
89
  # Store in Zep
86
90
  if not self.zep:
@@ -107,17 +111,21 @@ class MemoryRepository(MemoryProvider):
107
111
  try:
108
112
  await self.zep.user.add(user_id=user_id)
109
113
  except Exception as e:
110
- print(f"Zep user addition error: {e}")
114
+ logger.error(
115
+ f"Zep user addition error: {e}"
116
+ ) # Use logger.error
111
117
 
112
118
  try:
113
119
  await self.zep.memory.add_session(
114
120
  session_id=user_id, user_id=user_id
115
121
  )
116
122
  except Exception as e:
117
- print(f"Zep session creation error: {e}")
123
+ logger.error(
124
+ f"Zep session creation error: {e}"
125
+ ) # Use logger.error
118
126
  await self.zep.memory.add(session_id=user_id, messages=zep_messages)
119
127
  except Exception as e:
120
- print(f"Zep memory addition error: {e}")
128
+ logger.error(f"Zep memory addition error: {e}") # Use logger.error
121
129
  return
122
130
 
123
131
  async def retrieve(self, user_id: str) -> str:
@@ -131,7 +139,7 @@ class MemoryRepository(MemoryProvider):
131
139
  return memories
132
140
 
133
141
  except Exception as e:
134
- print(f"Error retrieving memories: {e}")
142
+ logger.error(f"Error retrieving memories: {e}") # Use logger.error
135
143
  return ""
136
144
 
137
145
  async def delete(self, user_id: str) -> None:
@@ -140,7 +148,7 @@ class MemoryRepository(MemoryProvider):
140
148
  try:
141
149
  self.mongo.delete_all(self.collection, {"user_id": user_id})
142
150
  except Exception as e:
143
- print(f"MongoDB deletion error: {e}")
151
+ logger.error(f"MongoDB deletion error: {e}") # Use logger.error
144
152
 
145
153
  if not self.zep:
146
154
  return
@@ -148,12 +156,12 @@ class MemoryRepository(MemoryProvider):
148
156
  try:
149
157
  await self.zep.memory.delete(session_id=user_id)
150
158
  except Exception as e:
151
- print(f"Zep memory deletion error: {e}")
159
+ logger.error(f"Zep memory deletion error: {e}") # Use logger.error
152
160
 
153
161
  try:
154
162
  await self.zep.user.delete(user_id=user_id)
155
163
  except Exception as e:
156
- print(f"Zep user deletion error: {e}")
164
+ logger.error(f"Zep user deletion error: {e}") # Use logger.error
157
165
 
158
166
  def find(
159
167
  self,
@@ -170,7 +178,7 @@ class MemoryRepository(MemoryProvider):
170
178
  try:
171
179
  return self.mongo.find(collection, query, sort=sort, limit=limit, skip=skip)
172
180
  except Exception as e:
173
- print(f"MongoDB find error: {e}")
181
+ logger.error(f"MongoDB find error: {e}") # Use logger.error
174
182
  return []
175
183
 
176
184
  def count_documents(self, collection: str, query: Dict) -> int: