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 +7 -2
- solana_agent/factories/agent_factory.py +53 -1
- solana_agent/guardrails/pii.py +107 -0
- solana_agent/interfaces/guardrails/guardrails.py +26 -0
- solana_agent/services/agent.py +392 -237
- solana_agent/services/query.py +140 -58
- {solana_agent-27.4.3.dist-info → solana_agent-27.5.0.dist-info}/METADATA +83 -3
- {solana_agent-27.4.3.dist-info → solana_agent-27.5.0.dist-info}/RECORD +10 -8
- {solana_agent-27.4.3.dist-info → solana_agent-27.5.0.dist-info}/LICENSE +0 -0
- {solana_agent-27.4.3.dist-info → solana_agent-27.5.0.dist-info}/WHEEL +0 -0
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
|
-
|
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
|