solana-agent 27.4.3__py3-none-any.whl → 27.5.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 CHANGED
@@ -5,8 +5,6 @@ This package provides a modular framework for building AI agent systems with
5
5
  multiple specialized agents, memory management, and conversation routing.
6
6
  """
7
7
 
8
- __version__ = "14.0.0" # Update with your actual version
9
-
10
8
  # Client interface (main entry point)
11
9
  from solana_agent.client.solana_agent import SolanaAgent
12
10
 
@@ -17,6 +15,10 @@ from solana_agent.factories.agent_factory import SolanaAgentFactory
17
15
  from solana_agent.plugins.manager import PluginManager
18
16
  from solana_agent.plugins.registry import ToolRegistry
19
17
  from solana_agent.plugins.tools.auto_tool import AutoTool
18
+ from solana_agent.interfaces.guardrails.guardrails import (
19
+ InputGuardrail,
20
+ OutputGuardrail,
21
+ )
20
22
 
21
23
  # Package metadata
22
24
  __all__ = [
@@ -28,4 +30,7 @@ __all__ = [
28
30
  "PluginManager",
29
31
  "ToolRegistry",
30
32
  "AutoTool",
33
+ # Guardrails
34
+ "InputGuardrail",
35
+ "OutputGuardrail",
31
36
  ]
@@ -5,10 +5,15 @@ This module handles the creation and dependency injection for all
5
5
  services and components used in the system.
6
6
  """
7
7
 
8
- from typing import Dict, Any
8
+ import importlib
9
+ from typing import Dict, Any, List
9
10
 
10
11
  # Service imports
11
12
  from solana_agent.adapters.pinecone_adapter import PineconeAdapter
13
+ from solana_agent.interfaces.guardrails.guardrails import (
14
+ InputGuardrail,
15
+ OutputGuardrail,
16
+ )
12
17
  from solana_agent.services.query import QueryService
13
18
  from solana_agent.services.agent import AgentService
14
19
  from solana_agent.services.routing import RoutingService
@@ -29,6 +34,37 @@ from solana_agent.plugins.manager import PluginManager
29
34
  class SolanaAgentFactory:
30
35
  """Factory for creating and wiring components of the Solana Agent system."""
31
36
 
37
+ @staticmethod
38
+ def _create_guardrails(guardrail_configs: List[Dict[str, Any]]) -> List[Any]:
39
+ """Instantiates guardrails from configuration."""
40
+ guardrails = []
41
+ if not guardrail_configs:
42
+ return guardrails
43
+
44
+ for config in guardrail_configs:
45
+ class_path = config.get("class")
46
+ guardrail_config = config.get("config", {})
47
+ if not class_path:
48
+ print(f"Guardrail config missing 'class': {config}")
49
+ continue
50
+ try:
51
+ module_path, class_name = class_path.rsplit(".", 1)
52
+ module = importlib.import_module(module_path)
53
+ guardrail_class = getattr(module, class_name)
54
+ # Instantiate the guardrail, handling potential errors during init
55
+ try:
56
+ guardrails.append(guardrail_class(config=guardrail_config))
57
+ print(f"Successfully loaded guardrail: {class_path}")
58
+ except Exception as init_e:
59
+ print(f"Error initializing guardrail '{class_path}': {init_e}")
60
+ # Optionally re-raise or just skip this guardrail
61
+
62
+ except (ImportError, AttributeError, ValueError) as e:
63
+ print(f"Error loading guardrail class '{class_path}': {e}")
64
+ except Exception as e: # Catch unexpected errors during import/getattr
65
+ print(f"Unexpected error loading guardrail '{class_path}': {e}")
66
+ return guardrails
67
+
32
68
  @staticmethod
33
69
  def create_from_config(config: Dict[str, Any]) -> QueryService:
34
70
  """Create the agent system from configuration.
@@ -83,6 +119,17 @@ class SolanaAgentFactory:
83
119
  raise ValueError("Zep API key is required.")
84
120
  memory_provider = MemoryRepository(zep_api_key=config["zep"].get("api_key"))
85
121
 
122
+ guardrail_config = config.get("guardrails", {})
123
+ input_guardrails: List[InputGuardrail] = SolanaAgentFactory._create_guardrails(
124
+ guardrail_config.get("input", [])
125
+ )
126
+ output_guardrails: List[OutputGuardrail] = (
127
+ SolanaAgentFactory._create_guardrails(guardrail_config.get("output", []))
128
+ )
129
+ print(
130
+ f"Loaded {len(input_guardrails)} input guardrails and {len(output_guardrails)} output guardrails."
131
+ )
132
+
86
133
  if (
87
134
  "gemini" in config
88
135
  and "api_key" in config["gemini"]
@@ -96,6 +143,7 @@ class SolanaAgentFactory:
96
143
  api_key=config["gemini"]["api_key"],
97
144
  base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
98
145
  model="gemini-2.5-flash-preview-04-17",
146
+ output_guardrails=output_guardrails,
99
147
  )
100
148
 
101
149
  # Create routing service
@@ -121,6 +169,7 @@ class SolanaAgentFactory:
121
169
  api_key=config["grok"]["api_key"],
122
170
  base_url="https://api.x.ai/v1",
123
171
  model="grok-3-mini-fast-beta",
172
+ output_guardrails=output_guardrails,
124
173
  )
125
174
  # Create routing service
126
175
  routing_service = RoutingService(
@@ -142,6 +191,7 @@ class SolanaAgentFactory:
142
191
  api_key=config["grok"]["api_key"],
143
192
  base_url="https://api.x.ai/v1",
144
193
  model="grok-3-mini-fast-beta",
194
+ output_guardrails=output_guardrails,
145
195
  )
146
196
 
147
197
  # Create routing service
@@ -156,6 +206,7 @@ class SolanaAgentFactory:
156
206
  llm_provider=llm_adapter,
157
207
  business_mission=business_mission,
158
208
  config=config,
209
+ output_guardrails=output_guardrails,
159
210
  )
160
211
 
161
212
  # Create routing service
@@ -284,6 +335,7 @@ class SolanaAgentFactory:
284
335
  memory_provider=memory_provider,
285
336
  knowledge_base=knowledge_base, # Pass the potentially created KB
286
337
  kb_results_count=kb_config.get("results_count", 3) if kb_config else 3,
338
+ input_guardrails=input_guardrails,
287
339
  )
288
340
 
289
341
  return query_service
@@ -0,0 +1,107 @@
1
+ import logging
2
+ from typing import Dict, Any, Optional, List
3
+ import scrubadub
4
+ from solana_agent.interfaces.guardrails.guardrails import (
5
+ InputGuardrail,
6
+ OutputGuardrail,
7
+ )
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class PII(InputGuardrail, OutputGuardrail):
13
+ """
14
+ A guardrail using Scrubadub to detect and remove PII.
15
+
16
+ Requires 'scrubadub'. Install with: pip install solana-agent[guardrails]
17
+ """
18
+
19
+ DEFAULT_REPLACEMENT = "[REDACTED_{detector_name}]"
20
+ DEFAULT_LANG = "en_US" # Scrubadub uses locale format
21
+
22
+ def __init__(self, config: Dict[str, Any] = None):
23
+ super().__init__(config)
24
+ self.replacement_format = self.config.get(
25
+ "replacement", self.DEFAULT_REPLACEMENT
26
+ )
27
+ self.locale = self.config.get("locale", self.DEFAULT_LANG)
28
+ # Optional: Specify detectors to use, None uses defaults
29
+ self.detector_list: Optional[List[str]] = self.config.get("detectors")
30
+ # Optional: Add custom detectors if needed via config
31
+ self.extra_detector_list = self.config.get(
32
+ "extra_detectors", []
33
+ ) # List of detector classes/instances
34
+
35
+ try:
36
+ # Initialize Scrubber
37
+ # Note: detector_list expects instances, not names. Need mapping or direct instantiation if customizing.
38
+ # For simplicity, we'll use defaults or allow passing instances via config (advanced).
39
+ # Using default detectors if self.detector_list is None.
40
+ if self.detector_list is not None:
41
+ logger.warning(
42
+ "Customizing 'detectors' by name list is not directly supported here yet. Using defaults."
43
+ )
44
+ # TODO: Add logic to map names to detector classes if needed.
45
+ self.scrubber = scrubadub.Scrubber(locale=self.locale)
46
+ else:
47
+ self.scrubber = scrubadub.Scrubber(locale=self.locale)
48
+
49
+ # Add any extra detectors passed via config (e.g., custom regex detectors)
50
+ for detector in self.extra_detector_list:
51
+ # Assuming extra_detectors are already instantiated objects
52
+ # Or add logic here to instantiate them based on class paths/names
53
+ if isinstance(detector, scrubadub.detectors.Detector):
54
+ self.scrubber.add_detector(detector)
55
+ else:
56
+ logger.warning(f"Invalid item in extra_detectors: {detector}")
57
+
58
+ logger.info(f"ScrubadubPIIFilter initialized for locale '{self.locale}'")
59
+
60
+ except ImportError:
61
+ logger.error(
62
+ "Scrubadub not installed. Please install with 'pip install solana-agent[guardrails]'"
63
+ )
64
+ raise
65
+ except Exception as e:
66
+ logger.error(f"Failed to initialize Scrubadub: {e}", exc_info=True)
67
+ raise
68
+
69
+ async def process(self, text: str) -> str:
70
+ """Clean text using Scrubadub."""
71
+ try:
72
+ # Scrubadub's clean method handles the replacement logic.
73
+ # We need to customize the replacement format per detector.
74
+ # This requires iterating through filth found first.
75
+
76
+ clean_text = text
77
+ filth_list = list(self.scrubber.iter_filth(text)) # Get all findings
78
+
79
+ if not filth_list:
80
+ return text
81
+
82
+ # Sort by start index to handle replacements correctly
83
+ filth_list.sort(key=lambda f: f.beg)
84
+
85
+ offset = 0
86
+ for filth in filth_list:
87
+ start = filth.beg + offset
88
+ end = filth.end + offset
89
+ replacement_text = self.replacement_format.format(
90
+ detector_name=filth.detector_name,
91
+ text=filth.text,
92
+ locale=filth.locale,
93
+ # Add other filth attributes if needed in format string
94
+ )
95
+
96
+ clean_text = clean_text[:start] + replacement_text + clean_text[end:]
97
+ offset += len(replacement_text) - (filth.end - filth.beg)
98
+
99
+ if clean_text != text:
100
+ logger.debug(
101
+ f"ScrubadubPIIFilter redacted {len(filth_list)} pieces of filth."
102
+ )
103
+ return clean_text
104
+
105
+ except Exception as e:
106
+ logger.error(f"Error during Scrubadub cleaning: {e}", exc_info=True)
107
+ return text # Return original text on error
@@ -0,0 +1,26 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict
3
+
4
+
5
+ class Guardrail(ABC):
6
+ """Base class for all guardrails."""
7
+
8
+ def __init__(self, config: Dict[str, Any] = None):
9
+ self.config = config or {}
10
+
11
+ @abstractmethod
12
+ async def process(self, text: str) -> str:
13
+ """Process the text and return the modified text."""
14
+ pass
15
+
16
+
17
+ class InputGuardrail(Guardrail):
18
+ """Interface for guardrails applied to user input."""
19
+
20
+ pass
21
+
22
+
23
+ class OutputGuardrail(Guardrail):
24
+ """Interface for guardrails applied to agent output."""
25
+
26
+ pass