solana-agent 27.4.2__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.
Files changed (42) hide show
  1. {solana_agent-27.4.2 → solana_agent-27.5.0}/PKG-INFO +84 -3
  2. {solana_agent-27.4.2 → solana_agent-27.5.0}/README.md +81 -2
  3. {solana_agent-27.4.2 → solana_agent-27.5.0}/pyproject.toml +3 -4
  4. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/__init__.py +7 -2
  5. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/factories/agent_factory.py +53 -1
  6. solana_agent-27.5.0/solana_agent/guardrails/pii.py +107 -0
  7. solana_agent-27.5.0/solana_agent/interfaces/guardrails/guardrails.py +26 -0
  8. solana_agent-27.5.0/solana_agent/services/agent.py +838 -0
  9. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/services/query.py +140 -58
  10. solana_agent-27.4.2/solana_agent/services/agent.py +0 -683
  11. {solana_agent-27.4.2 → solana_agent-27.5.0}/LICENSE +0 -0
  12. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/adapters/__init__.py +0 -0
  13. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/adapters/mongodb_adapter.py +0 -0
  14. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/adapters/openai_adapter.py +0 -0
  15. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/adapters/pinecone_adapter.py +0 -0
  16. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/client/__init__.py +0 -0
  17. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/client/solana_agent.py +0 -0
  18. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/domains/__init__.py +0 -0
  19. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/domains/agent.py +0 -0
  20. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/domains/routing.py +0 -0
  21. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/factories/__init__.py +0 -0
  22. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/__init__.py +0 -0
  23. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/client/client.py +0 -0
  24. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/plugins/plugins.py +0 -0
  25. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/providers/data_storage.py +0 -0
  26. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/providers/llm.py +0 -0
  27. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/providers/memory.py +0 -0
  28. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/providers/vector_storage.py +0 -0
  29. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/services/agent.py +0 -0
  30. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/services/knowledge_base.py +0 -0
  31. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/services/query.py +0 -0
  32. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/interfaces/services/routing.py +0 -0
  33. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/plugins/__init__.py +0 -0
  34. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/plugins/manager.py +0 -0
  35. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/plugins/registry.py +0 -0
  36. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/plugins/tools/__init__.py +0 -0
  37. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/plugins/tools/auto_tool.py +0 -0
  38. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/repositories/__init__.py +0 -0
  39. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/repositories/memory.py +0 -0
  40. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/services/__init__.py +0 -0
  41. {solana_agent-27.4.2 → solana_agent-27.5.0}/solana_agent/services/knowledge_base.py +0 -0
  42. {solana_agent-27.4.2 → 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.4.2
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,8 +22,10 @@ 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
28
+ Project-URL: Homepage, https://solana-agent.com
27
29
  Project-URL: Repository, https://github.com/truemagic-coder/solana-agent
28
30
  Description-Content-Type: text/markdown
29
31
 
@@ -59,9 +61,10 @@ Build your AI agents in three lines of code!
59
61
  * Extensible Tooling
60
62
  * Knowledge Base
61
63
  * MCP Support
64
+ * Guardrails
62
65
  * Tested & Secure
63
66
  * Built in Python
64
- * Powers [CometHeart](https://cometheart.com) & [WalletBubbles](https://walletbubbles.com)
67
+ * Powers [CometHeart](https://cometheart.com)
65
68
 
66
69
  ## Features
67
70
 
@@ -80,6 +83,7 @@ Build your AI agents in three lines of code!
80
83
  * Powerful tool integration using standard Python packages and/or inline tools
81
84
  * Assigned tools are utilized by agents automatically and effectively
82
85
  * Integrated Knowledge Base with semantic search and automatic PDF chunking
86
+ * Input and output guardrails for content filtering, safety, and data sanitization
83
87
 
84
88
  ## Stack
85
89
 
@@ -447,6 +451,84 @@ async for response in solana_agent.process("user123", "Summarize the annual repo
447
451
  print(response, end="")
448
452
  ```
449
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
+
450
532
  ## Tools
451
533
 
452
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.
@@ -527,7 +609,6 @@ Other MCP servers may work but are not supported.
527
609
  `pip install sakit`
528
610
 
529
611
  ```python
530
-
531
612
  from solana_agent import SolanaAgent
532
613
 
533
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) & [WalletBubbles](https://walletbubbles.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,10 +1,11 @@
1
1
  [tool.poetry]
2
2
  name = "solana-agent"
3
- version = "27.4.2"
3
+ version = "27.5.0"
4
4
  description = "AI Agents for Solana"
5
5
  authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
6
6
  license = "MIT"
7
7
  readme = "README.md"
8
+ homepage = "https://solana-agent.com"
8
9
  repository = "https://github.com/truemagic-coder/solana-agent"
9
10
  documentation = "https://docs.solana-agent.com"
10
11
  keywords = ["solana", "solana ai", "solana agent", "ai", "ai agent", "ai agents"]
@@ -32,6 +33,7 @@ pinecone = "^6.0.2"
32
33
  llama-index-core = "^0.12.30"
33
34
  llama-index-embeddings-openai = "^0.3.1"
34
35
  pypdf = "^5.4.0"
36
+ scrubadub = "^2.0.1"
35
37
 
36
38
  [tool.poetry.group.dev.dependencies]
37
39
  pytest = "^8.3.5"
@@ -46,9 +48,6 @@ sphinx-autobuild = "^2024.10.3"
46
48
  mongomock = "^4.3.0"
47
49
  ruff = "^0.11.6"
48
50
 
49
- [tool.setuptools.package-data]
50
- "solana-agent" = ["https://dl.walletbubbles.com/solana-agent-logo.png?width=50"]
51
-
52
51
  [build-system]
53
52
  requires = ["poetry-core>=1.0.0"]
54
53
  build-backend = "poetry.core.masonry.api"
@@ -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