solana-agent 27.4.3__tar.gz → 27.5.0__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-27.4.3 → solana_agent-27.5.0}/PKG-INFO +83 -3
- {solana_agent-27.4.3 → solana_agent-27.5.0}/README.md +81 -2
- {solana_agent-27.4.3 → solana_agent-27.5.0}/pyproject.toml +2 -1
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/__init__.py +7 -2
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/factories/agent_factory.py +53 -1
- solana_agent-27.5.0/solana_agent/guardrails/pii.py +107 -0
- solana_agent-27.5.0/solana_agent/interfaces/guardrails/guardrails.py +26 -0
- solana_agent-27.5.0/solana_agent/services/agent.py +838 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/services/query.py +140 -58
- solana_agent-27.4.3/solana_agent/services/agent.py +0 -683
- {solana_agent-27.4.3 → solana_agent-27.5.0}/LICENSE +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/adapters/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/adapters/mongodb_adapter.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/adapters/openai_adapter.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/adapters/pinecone_adapter.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/client/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/client/solana_agent.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/domains/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/domains/agent.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/domains/routing.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/factories/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/client/client.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/plugins/plugins.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/providers/data_storage.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/providers/llm.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/providers/memory.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/providers/vector_storage.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/services/agent.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/services/knowledge_base.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/services/query.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/interfaces/services/routing.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/plugins/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/plugins/manager.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/plugins/registry.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/plugins/tools/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/plugins/tools/auto_tool.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/repositories/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/repositories/memory.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/services/__init__.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/services/knowledge_base.py +0 -0
- {solana_agent-27.4.3 → solana_agent-27.5.0}/solana_agent/services/routing.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: solana-agent
|
3
|
-
Version: 27.
|
3
|
+
Version: 27.5.0
|
4
4
|
Summary: AI Agents for Solana
|
5
5
|
License: MIT
|
6
6
|
Keywords: solana,solana ai,solana agent,ai,ai agent,ai agents
|
@@ -22,6 +22,7 @@ Requires-Dist: pinecone (>=6.0.2,<7.0.0)
|
|
22
22
|
Requires-Dist: pydantic (>=2)
|
23
23
|
Requires-Dist: pymongo (>=4.12.0,<5.0.0)
|
24
24
|
Requires-Dist: pypdf (>=5.4.0,<6.0.0)
|
25
|
+
Requires-Dist: scrubadub (>=2.0.1,<3.0.0)
|
25
26
|
Requires-Dist: zep-cloud (>=2.10.1,<3.0.0)
|
26
27
|
Project-URL: Documentation, https://docs.solana-agent.com
|
27
28
|
Project-URL: Homepage, https://solana-agent.com
|
@@ -60,9 +61,10 @@ Build your AI agents in three lines of code!
|
|
60
61
|
* Extensible Tooling
|
61
62
|
* Knowledge Base
|
62
63
|
* MCP Support
|
64
|
+
* Guardrails
|
63
65
|
* Tested & Secure
|
64
66
|
* Built in Python
|
65
|
-
* Powers [CometHeart](https://cometheart.com)
|
67
|
+
* Powers [CometHeart](https://cometheart.com)
|
66
68
|
|
67
69
|
## Features
|
68
70
|
|
@@ -81,6 +83,7 @@ Build your AI agents in three lines of code!
|
|
81
83
|
* Powerful tool integration using standard Python packages and/or inline tools
|
82
84
|
* Assigned tools are utilized by agents automatically and effectively
|
83
85
|
* Integrated Knowledge Base with semantic search and automatic PDF chunking
|
86
|
+
* Input and output guardrails for content filtering, safety, and data sanitization
|
84
87
|
|
85
88
|
## Stack
|
86
89
|
|
@@ -448,6 +451,84 @@ async for response in solana_agent.process("user123", "Summarize the annual repo
|
|
448
451
|
print(response, end="")
|
449
452
|
```
|
450
453
|
|
454
|
+
### Guardrails
|
455
|
+
|
456
|
+
Guardrails allow you to process and potentially modify user input before it reaches the agent (Input Guardrails) and agent output before it's sent back to the user (Output Guardrails). This is useful for implementing safety checks, content moderation, data sanitization, or custom transformations.
|
457
|
+
|
458
|
+
Solana Agent provides a built-in PII scrubber based on [scrubadub](https://github.com/LeapBeyond/scrubadub).
|
459
|
+
|
460
|
+
```python
|
461
|
+
from solana_agent import SolanaAgent
|
462
|
+
|
463
|
+
config = {
|
464
|
+
"guardrails": {
|
465
|
+
"input": [
|
466
|
+
# Example using a custom input guardrail
|
467
|
+
{
|
468
|
+
"class": "MyInputGuardrail",
|
469
|
+
"config": {"setting1": "value1"}
|
470
|
+
},
|
471
|
+
# Example using the built-in PII guardrail for input
|
472
|
+
{
|
473
|
+
"class": "solana_agent.guardrails.pii.PII",
|
474
|
+
"config": {
|
475
|
+
"locale": "en_GB", # Optional: Specify locale (default: en_US)
|
476
|
+
"replacement": "[REDACTED]" # Optional: Custom replacement format
|
477
|
+
}
|
478
|
+
}
|
479
|
+
],
|
480
|
+
"output": [
|
481
|
+
# Example using a custom output guardrail
|
482
|
+
{
|
483
|
+
"class": "MyOutputGuardrail",
|
484
|
+
"config": {"filter_level": "high"}
|
485
|
+
},
|
486
|
+
# Example using the built-in PII guardrail for output (with defaults)
|
487
|
+
{
|
488
|
+
"class": "solana_agent.guardrails.pii.PII"
|
489
|
+
# No config needed to use defaults
|
490
|
+
}
|
491
|
+
]
|
492
|
+
},
|
493
|
+
}
|
494
|
+
```
|
495
|
+
|
496
|
+
#### Example Custom Guardrails
|
497
|
+
|
498
|
+
```python
|
499
|
+
from solana_agent import InputGuardrail, OutputGuardrail
|
500
|
+
import logging
|
501
|
+
|
502
|
+
logger = logging.getLogger(__name__)
|
503
|
+
|
504
|
+
class MyInputGuardrail(InputGuardrail):
|
505
|
+
def __init__(self, config=None):
|
506
|
+
super().__init__(config)
|
507
|
+
self.setting1 = self.config.get("setting1", "default_value")
|
508
|
+
logger.info(f"MyInputGuardrail initialized with setting1: {self.setting1}")
|
509
|
+
|
510
|
+
async def process(self, text: str) -> str:
|
511
|
+
# Example: Convert input to lowercase
|
512
|
+
processed_text = text.lower()
|
513
|
+
logger.debug(f"Input Guardrail processed: {text} -> {processed_text}")
|
514
|
+
return processed_text
|
515
|
+
|
516
|
+
class MyOutputGuardrail(OutputGuardrail):
|
517
|
+
def __init__(self, config=None):
|
518
|
+
super().__init__(config)
|
519
|
+
self.filter_level = self.config.get("filter_level", "low")
|
520
|
+
logger.info(f"MyOutputGuardrail initialized with filter_level: {self.filter_level}")
|
521
|
+
|
522
|
+
async def process(self, text: str) -> str:
|
523
|
+
# Example: Basic profanity filtering (replace with a real library)
|
524
|
+
if self.filter_level == "high" and "badword" in text:
|
525
|
+
processed_text = text.replace("badword", "*******")
|
526
|
+
logger.warning(f"Output Guardrail filtered content.")
|
527
|
+
return processed_text
|
528
|
+
logger.debug("Output Guardrail passed text through.")
|
529
|
+
return text
|
530
|
+
```
|
531
|
+
|
451
532
|
## Tools
|
452
533
|
|
453
534
|
Tools can be used from plugins like Solana Agent Kit (sakit) or via inline tools. Tools available via plugins integrate automatically with Solana Agent.
|
@@ -528,7 +609,6 @@ Other MCP servers may work but are not supported.
|
|
528
609
|
`pip install sakit`
|
529
610
|
|
530
611
|
```python
|
531
|
-
|
532
612
|
from solana_agent import SolanaAgent
|
533
613
|
|
534
614
|
config = {
|
@@ -30,9 +30,10 @@ Build your AI agents in three lines of code!
|
|
30
30
|
* Extensible Tooling
|
31
31
|
* Knowledge Base
|
32
32
|
* MCP Support
|
33
|
+
* Guardrails
|
33
34
|
* Tested & Secure
|
34
35
|
* Built in Python
|
35
|
-
* Powers [CometHeart](https://cometheart.com)
|
36
|
+
* Powers [CometHeart](https://cometheart.com)
|
36
37
|
|
37
38
|
## Features
|
38
39
|
|
@@ -51,6 +52,7 @@ Build your AI agents in three lines of code!
|
|
51
52
|
* Powerful tool integration using standard Python packages and/or inline tools
|
52
53
|
* Assigned tools are utilized by agents automatically and effectively
|
53
54
|
* Integrated Knowledge Base with semantic search and automatic PDF chunking
|
55
|
+
* Input and output guardrails for content filtering, safety, and data sanitization
|
54
56
|
|
55
57
|
## Stack
|
56
58
|
|
@@ -418,6 +420,84 @@ async for response in solana_agent.process("user123", "Summarize the annual repo
|
|
418
420
|
print(response, end="")
|
419
421
|
```
|
420
422
|
|
423
|
+
### Guardrails
|
424
|
+
|
425
|
+
Guardrails allow you to process and potentially modify user input before it reaches the agent (Input Guardrails) and agent output before it's sent back to the user (Output Guardrails). This is useful for implementing safety checks, content moderation, data sanitization, or custom transformations.
|
426
|
+
|
427
|
+
Solana Agent provides a built-in PII scrubber based on [scrubadub](https://github.com/LeapBeyond/scrubadub).
|
428
|
+
|
429
|
+
```python
|
430
|
+
from solana_agent import SolanaAgent
|
431
|
+
|
432
|
+
config = {
|
433
|
+
"guardrails": {
|
434
|
+
"input": [
|
435
|
+
# Example using a custom input guardrail
|
436
|
+
{
|
437
|
+
"class": "MyInputGuardrail",
|
438
|
+
"config": {"setting1": "value1"}
|
439
|
+
},
|
440
|
+
# Example using the built-in PII guardrail for input
|
441
|
+
{
|
442
|
+
"class": "solana_agent.guardrails.pii.PII",
|
443
|
+
"config": {
|
444
|
+
"locale": "en_GB", # Optional: Specify locale (default: en_US)
|
445
|
+
"replacement": "[REDACTED]" # Optional: Custom replacement format
|
446
|
+
}
|
447
|
+
}
|
448
|
+
],
|
449
|
+
"output": [
|
450
|
+
# Example using a custom output guardrail
|
451
|
+
{
|
452
|
+
"class": "MyOutputGuardrail",
|
453
|
+
"config": {"filter_level": "high"}
|
454
|
+
},
|
455
|
+
# Example using the built-in PII guardrail for output (with defaults)
|
456
|
+
{
|
457
|
+
"class": "solana_agent.guardrails.pii.PII"
|
458
|
+
# No config needed to use defaults
|
459
|
+
}
|
460
|
+
]
|
461
|
+
},
|
462
|
+
}
|
463
|
+
```
|
464
|
+
|
465
|
+
#### Example Custom Guardrails
|
466
|
+
|
467
|
+
```python
|
468
|
+
from solana_agent import InputGuardrail, OutputGuardrail
|
469
|
+
import logging
|
470
|
+
|
471
|
+
logger = logging.getLogger(__name__)
|
472
|
+
|
473
|
+
class MyInputGuardrail(InputGuardrail):
|
474
|
+
def __init__(self, config=None):
|
475
|
+
super().__init__(config)
|
476
|
+
self.setting1 = self.config.get("setting1", "default_value")
|
477
|
+
logger.info(f"MyInputGuardrail initialized with setting1: {self.setting1}")
|
478
|
+
|
479
|
+
async def process(self, text: str) -> str:
|
480
|
+
# Example: Convert input to lowercase
|
481
|
+
processed_text = text.lower()
|
482
|
+
logger.debug(f"Input Guardrail processed: {text} -> {processed_text}")
|
483
|
+
return processed_text
|
484
|
+
|
485
|
+
class MyOutputGuardrail(OutputGuardrail):
|
486
|
+
def __init__(self, config=None):
|
487
|
+
super().__init__(config)
|
488
|
+
self.filter_level = self.config.get("filter_level", "low")
|
489
|
+
logger.info(f"MyOutputGuardrail initialized with filter_level: {self.filter_level}")
|
490
|
+
|
491
|
+
async def process(self, text: str) -> str:
|
492
|
+
# Example: Basic profanity filtering (replace with a real library)
|
493
|
+
if self.filter_level == "high" and "badword" in text:
|
494
|
+
processed_text = text.replace("badword", "*******")
|
495
|
+
logger.warning(f"Output Guardrail filtered content.")
|
496
|
+
return processed_text
|
497
|
+
logger.debug("Output Guardrail passed text through.")
|
498
|
+
return text
|
499
|
+
```
|
500
|
+
|
421
501
|
## Tools
|
422
502
|
|
423
503
|
Tools can be used from plugins like Solana Agent Kit (sakit) or via inline tools. Tools available via plugins integrate automatically with Solana Agent.
|
@@ -498,7 +578,6 @@ Other MCP servers may work but are not supported.
|
|
498
578
|
`pip install sakit`
|
499
579
|
|
500
580
|
```python
|
501
|
-
|
502
581
|
from solana_agent import SolanaAgent
|
503
582
|
|
504
583
|
config = {
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "solana-agent"
|
3
|
-
version = "27.
|
3
|
+
version = "27.5.0"
|
4
4
|
description = "AI Agents for Solana"
|
5
5
|
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
|
6
6
|
license = "MIT"
|
@@ -33,6 +33,7 @@ pinecone = "^6.0.2"
|
|
33
33
|
llama-index-core = "^0.12.30"
|
34
34
|
llama-index-embeddings-openai = "^0.3.1"
|
35
35
|
pypdf = "^5.4.0"
|
36
|
+
scrubadub = "^2.0.1"
|
36
37
|
|
37
38
|
[tool.poetry.group.dev.dependencies]
|
38
39
|
pytest = "^8.3.5"
|
@@ -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
|