resklogits 0.1.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.
resklogits/__init__.py ADDED
@@ -0,0 +1,38 @@
1
+ """
2
+ ReskLogits - GPU-Accelerated Shadow Ban Logits Processor
3
+
4
+ Ultra-fast vectorized Aho-Corasick pattern matching for LLM safety filtering
5
+ with symbolic rule generation and intelligent caching.
6
+ """
7
+
8
+ from .cache import CachedGenerator, RuleCache
9
+ from .config_parser import ConfigParser, load_rules_from_yaml
10
+ from .pattern_automata import PatternExpander, SynonymGraph
11
+ from .rule_engine import ContainsRule, ExactRule, Rule, RuleEngine, StartsWithRule
12
+ from .rule_templates import Template, TemplateEngine
13
+ from .shadow_ban_processor import MultiLevelShadowBanProcessor, ShadowBanProcessor
14
+ from .vectorized_aho_corasick import VectorizedAhoCorasick
15
+
16
+ __version__ = "0.1.0"
17
+ __author__ = "RESK Team"
18
+ __all__ = [
19
+ # Core logits processors
20
+ "VectorizedAhoCorasick",
21
+ "ShadowBanProcessor",
22
+ "MultiLevelShadowBanProcessor",
23
+ # Rule generation
24
+ "ConfigParser",
25
+ "load_rules_from_yaml",
26
+ "TemplateEngine",
27
+ "Template",
28
+ "RuleEngine",
29
+ "Rule",
30
+ "ExactRule",
31
+ "StartsWithRule",
32
+ "ContainsRule",
33
+ "PatternExpander",
34
+ "SynonymGraph",
35
+ # Caching
36
+ "RuleCache",
37
+ "CachedGenerator",
38
+ ]
resklogits/cache.py ADDED
@@ -0,0 +1,269 @@
1
+ """
2
+ Cache System for Rule Generation
3
+
4
+ Provides hash-based caching to avoid regenerating patterns.
5
+ """
6
+
7
+ import hashlib
8
+ import json
9
+ import time
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional, cast
13
+
14
+
15
+ class RuleCache:
16
+ """
17
+ Hash-based cache for generated patterns.
18
+
19
+ Cache structure:
20
+ .resklogits_cache/
21
+ ├── index.json # Cache metadata index
22
+ ├── <hash>_patterns.json # Generated patterns
23
+ └── <hash>_meta.json # Generation metadata
24
+ """
25
+
26
+ def __init__(self, cache_dir: str = ".resklogits_cache"):
27
+ self.cache_dir = Path(cache_dir)
28
+ self.cache_dir.mkdir(exist_ok=True)
29
+
30
+ self.index_file = self.cache_dir / "index.json"
31
+ self.index = self._load_index()
32
+
33
+ def _load_index(self) -> Dict[str, Any]:
34
+ """Load cache index from disk."""
35
+ if self.index_file.exists():
36
+ with open(self.index_file, "r", encoding="utf-8") as f:
37
+ return cast(Dict[str, Any], json.load(f))
38
+ return {"entries": {}, "version": "1.0"}
39
+
40
+ def _save_index(self):
41
+ """Save cache index to disk."""
42
+ with open(self.index_file, "w", encoding="utf-8") as f:
43
+ json.dump(self.index, f, indent=2)
44
+
45
+ def compute_hash(self, content: str) -> str:
46
+ """
47
+ Compute SHA256 hash of content.
48
+
49
+ Args:
50
+ content: Content to hash (usually YAML rules)
51
+
52
+ Returns:
53
+ Hex hash string
54
+ """
55
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()[:16]
56
+
57
+ def exists(self, rule_hash: str) -> bool:
58
+ """Check if cache entry exists for given hash."""
59
+ pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
60
+ return pattern_file.exists()
61
+
62
+ def load(self, rule_hash: str) -> List[str]:
63
+ """
64
+ Load patterns from cache.
65
+
66
+ Args:
67
+ rule_hash: Hash of rules
68
+
69
+ Returns:
70
+ List of cached patterns
71
+
72
+ Raises:
73
+ FileNotFoundError: If cache entry doesn't exist
74
+ """
75
+ pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
76
+
77
+ if not pattern_file.exists():
78
+ raise FileNotFoundError(f"Cache entry not found: {rule_hash}")
79
+
80
+ with open(pattern_file, "r", encoding="utf-8") as f:
81
+ data = json.load(f)
82
+
83
+ # Update access time in index
84
+ if rule_hash in self.index["entries"]:
85
+ self.index["entries"][rule_hash]["last_accessed"] = time.time()
86
+ self._save_index()
87
+
88
+ return cast(List[str], data.get("patterns", []))
89
+
90
+ def save(self, rule_hash: str, patterns: List[str], metadata: Optional[Dict[str, Any]] = None):
91
+ """
92
+ Save patterns to cache.
93
+
94
+ Args:
95
+ rule_hash: Hash of rules
96
+ patterns: Generated patterns
97
+ metadata: Optional metadata about generation
98
+ """
99
+ # Save patterns
100
+ pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
101
+ with open(pattern_file, "w", encoding="utf-8") as f:
102
+ json.dump(
103
+ {"patterns": patterns, "count": len(patterns), "created": time.time()}, f, indent=2
104
+ )
105
+
106
+ # Save metadata
107
+ if metadata:
108
+ meta_file = self.cache_dir / f"{rule_hash}_meta.json"
109
+ with open(meta_file, "w", encoding="utf-8") as f:
110
+ json.dump(metadata, f, indent=2)
111
+
112
+ # Update index
113
+ self.index["entries"][rule_hash] = {
114
+ "created": time.time(),
115
+ "last_accessed": time.time(),
116
+ "pattern_count": len(patterns),
117
+ "has_metadata": metadata is not None,
118
+ }
119
+ self._save_index()
120
+
121
+ def get_metadata(self, rule_hash: str) -> Optional[Dict[str, Any]]:
122
+ """Load metadata for cached entry."""
123
+ meta_file = self.cache_dir / f"{rule_hash}_meta.json"
124
+
125
+ if not meta_file.exists():
126
+ return None
127
+
128
+ with open(meta_file, "r", encoding="utf-8") as f:
129
+ return cast(Optional[Dict[str, Any]], json.load(f))
130
+
131
+ def list_entries(self) -> List[Dict[str, Any]]:
132
+ """List all cache entries with metadata."""
133
+ entries = []
134
+ for hash_key, info in self.index["entries"].items():
135
+ entry = {
136
+ "hash": hash_key,
137
+ **info,
138
+ "created_str": datetime.fromtimestamp(info["created"]).isoformat(),
139
+ "last_accessed_str": datetime.fromtimestamp(info["last_accessed"]).isoformat(),
140
+ }
141
+ entries.append(entry)
142
+
143
+ # Sort by last accessed (most recent first)
144
+ entries.sort(key=lambda x: x["last_accessed"], reverse=True)
145
+ return entries
146
+
147
+ def clear(self, rule_hash: Optional[str] = None):
148
+ """
149
+ Clear cache entries.
150
+
151
+ Args:
152
+ rule_hash: Specific hash to clear (None = clear all)
153
+ """
154
+ if rule_hash:
155
+ # Clear specific entry
156
+ pattern_file = self.cache_dir / f"{rule_hash}_patterns.json"
157
+ meta_file = self.cache_dir / f"{rule_hash}_meta.json"
158
+
159
+ if pattern_file.exists():
160
+ pattern_file.unlink()
161
+ if meta_file.exists():
162
+ meta_file.unlink()
163
+
164
+ # Remove from index
165
+ if rule_hash in self.index["entries"]:
166
+ del self.index["entries"][rule_hash]
167
+ self._save_index()
168
+ else:
169
+ # Clear all entries
170
+ for file in self.cache_dir.glob("*_patterns.json"):
171
+ file.unlink()
172
+ for file in self.cache_dir.glob("*_meta.json"):
173
+ file.unlink()
174
+
175
+ self.index["entries"] = {}
176
+ self._save_index()
177
+
178
+ def get_stats(self) -> Dict[str, Any]:
179
+ """Get cache statistics."""
180
+ total_entries = len(self.index["entries"])
181
+ total_patterns = sum(
182
+ entry.get("pattern_count", 0) for entry in self.index["entries"].values()
183
+ )
184
+
185
+ # Calculate total size
186
+ total_size = 0
187
+ for file in self.cache_dir.glob("*.json"):
188
+ total_size += file.stat().st_size
189
+
190
+ return {
191
+ "total_entries": total_entries,
192
+ "total_patterns": total_patterns,
193
+ "cache_size_bytes": total_size,
194
+ "cache_size_mb": total_size / (1024 * 1024),
195
+ "cache_dir": str(self.cache_dir),
196
+ }
197
+
198
+ def cleanup_old(self, max_age_days: int = 30):
199
+ """
200
+ Remove cache entries older than specified age.
201
+
202
+ Args:
203
+ max_age_days: Maximum age in days
204
+ """
205
+ current_time = time.time()
206
+ max_age_seconds = max_age_days * 24 * 60 * 60
207
+
208
+ to_remove = []
209
+ for hash_key, info in self.index["entries"].items():
210
+ age = current_time - info["last_accessed"]
211
+ if age > max_age_seconds:
212
+ to_remove.append(hash_key)
213
+
214
+ for hash_key in to_remove:
215
+ self.clear(hash_key)
216
+
217
+ def __repr__(self):
218
+ stats = self.get_stats()
219
+ return f"RuleCache({stats['total_entries']} entries, {stats['total_patterns']} patterns)"
220
+
221
+
222
+ class CachedGenerator:
223
+ """Wrapper that adds caching to any generator function."""
224
+
225
+ def __init__(self, generator_func, cache: Optional[RuleCache] = None):
226
+ """
227
+ Initialize cached generator.
228
+
229
+ Args:
230
+ generator_func: Function that generates patterns
231
+ cache: RuleCache instance (creates default if None)
232
+ """
233
+ self.generator_func = generator_func
234
+ self.cache = cache or RuleCache()
235
+
236
+ def generate(self, rules_content: str, force: bool = False, **kwargs) -> List[str]:
237
+ """
238
+ Generate patterns with caching.
239
+
240
+ Args:
241
+ rules_content: Rule content to hash
242
+ force: Force regeneration even if cached
243
+ **kwargs: Additional arguments for generator function
244
+
245
+ Returns:
246
+ Generated patterns
247
+ """
248
+ # Compute hash
249
+ rule_hash = self.cache.compute_hash(rules_content)
250
+
251
+ # Check cache unless force regeneration
252
+ if not force and self.cache.exists(rule_hash):
253
+ return self.cache.load(rule_hash)
254
+
255
+ # Generate patterns
256
+ patterns = cast(List[str], self.generator_func(rules_content, **kwargs))
257
+
258
+ # Save to cache
259
+ metadata = {
260
+ "generator": self.generator_func.__name__,
261
+ "kwargs": kwargs,
262
+ "timestamp": time.time(),
263
+ }
264
+ self.cache.save(rule_hash, patterns, metadata)
265
+
266
+ return patterns
267
+
268
+ def __repr__(self):
269
+ return f"CachedGenerator({self.generator_func.__name__}, {self.cache})"
resklogits/cli.py ADDED
@@ -0,0 +1,311 @@
1
+ """
2
+ CLI Tool for Rule Generation
3
+
4
+ Command-line interface for generating, testing, and managing rules.
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from .cache import RuleCache
13
+ from .config_parser import ConfigParser, load_rules_from_yaml
14
+
15
+
16
+ def cmd_generate(args):
17
+ """Generate patterns from YAML rules."""
18
+ print(f"Generating patterns from: {args.rules}")
19
+
20
+ try:
21
+ parser = ConfigParser()
22
+ results = parser.generate_all_patterns(
23
+ args.rules, use_synonyms=args.synonyms, use_cache=not args.no_cache, force=args.force
24
+ )
25
+
26
+ # Flatten patterns
27
+ all_patterns = []
28
+ for _rule_name, patterns in results.items():
29
+ all_patterns.extend(patterns)
30
+
31
+ unique_patterns = list(set(all_patterns))
32
+
33
+ print(f"✓ Generated {len(unique_patterns)} unique patterns from {len(results)} rules")
34
+
35
+ if args.output:
36
+ # Save to JSON
37
+ output_path = Path(args.output)
38
+ with open(output_path, "w", encoding="utf-8") as f:
39
+ if args.by_category:
40
+ json.dump(results, f, indent=2)
41
+ else:
42
+ json.dump(unique_patterns, f, indent=2)
43
+ print(f"✓ Saved to: {args.output}")
44
+ else:
45
+ # Print to stdout
46
+ if args.by_category:
47
+ print(json.dumps(results, indent=2))
48
+ else:
49
+ for pattern in sorted(unique_patterns)[: args.preview]:
50
+ print(f" - {pattern}")
51
+ if len(unique_patterns) > args.preview:
52
+ print(f" ... and {len(unique_patterns) - args.preview} more")
53
+
54
+ return 0
55
+
56
+ except Exception as e:
57
+ print(f"✗ Error: {e}", file=sys.stderr)
58
+ if args.verbose:
59
+ import traceback
60
+
61
+ traceback.print_exc()
62
+ return 1
63
+
64
+
65
+ def cmd_test(args):
66
+ """Test rule matching against text."""
67
+ print(f"Testing rules from: {args.rules}")
68
+ print(f'Test text: "{args.text}"')
69
+ print()
70
+
71
+ try:
72
+ patterns = load_rules_from_yaml(args.rules, use_cache=not args.no_cache)
73
+
74
+ matches = [p for p in patterns if p in args.text.lower()]
75
+
76
+ if matches:
77
+ print(f"✓ Found {len(matches)} matches:")
78
+ for match in matches:
79
+ print(f" - {match}")
80
+ else:
81
+ print("✗ No matches found")
82
+
83
+ return 0
84
+
85
+ except Exception as e:
86
+ print(f"✗ Error: {e}", file=sys.stderr)
87
+ return 1
88
+
89
+
90
+ def cmd_expand(args):
91
+ """Expand and preview patterns for a specific rule."""
92
+ print(f"Expanding rule: {args.rule}")
93
+
94
+ try:
95
+ parser = ConfigParser()
96
+ all_rules = parser.parse_file(args.rules)
97
+
98
+ if args.rule not in all_rules:
99
+ print(f"✗ Rule '{args.rule}' not found", file=sys.stderr)
100
+ print(f"Available rules: {', '.join(all_rules.keys())}")
101
+ return 1
102
+
103
+ rule_config = all_rules[args.rule]
104
+ patterns = parser.generate_patterns(rule_config, use_synonyms=not args.no_synonyms)
105
+
106
+ print(f"✓ Generated {len(patterns)} patterns:")
107
+ for pattern in sorted(patterns)[: args.preview]:
108
+ print(f" - {pattern}")
109
+ if len(patterns) > args.preview:
110
+ print(f" ... and {len(patterns) - args.preview} more")
111
+
112
+ return 0
113
+
114
+ except Exception as e:
115
+ print(f"✗ Error: {e}", file=sys.stderr)
116
+ return 1
117
+
118
+
119
+ def cmd_cache_status(args):
120
+ """Show cache status."""
121
+ cache = RuleCache(args.cache_dir)
122
+
123
+ stats = cache.get_stats()
124
+ print("Cache Status:")
125
+ print(f" Directory: {stats['cache_dir']}")
126
+ print(f" Entries: {stats['total_entries']}")
127
+ print(f" Total patterns: {stats['total_patterns']}")
128
+ print(f" Size: {stats['cache_size_mb']:.2f} MB")
129
+ print()
130
+
131
+ if args.verbose and stats["total_entries"] > 0:
132
+ print("Entries:")
133
+ for entry in cache.list_entries()[:10]:
134
+ print(
135
+ f" {entry['hash']}: {entry['pattern_count']} patterns (accessed: {entry['last_accessed_str']})"
136
+ )
137
+
138
+ return 0
139
+
140
+
141
+ def cmd_cache_clear(args):
142
+ """Clear cache."""
143
+ cache = RuleCache(args.cache_dir)
144
+
145
+ if args.hash:
146
+ cache.clear(args.hash)
147
+ print(f"✓ Cleared cache entry: {args.hash}")
148
+ else:
149
+ stats = cache.get_stats()
150
+ if (
151
+ args.yes
152
+ or input(f"Clear all {stats['total_entries']} cache entries? (y/N): ").lower() == "y"
153
+ ):
154
+ cache.clear()
155
+ print("✓ Cache cleared")
156
+ else:
157
+ print("Cancelled")
158
+
159
+ return 0
160
+
161
+
162
+ def cmd_cache_show(args):
163
+ """Show cache entry details."""
164
+ cache = RuleCache(args.cache_dir)
165
+
166
+ try:
167
+ patterns = cache.load(args.hash)
168
+ metadata = cache.get_metadata(args.hash)
169
+
170
+ print(f"Cache Entry: {args.hash}")
171
+ print(f" Patterns: {len(patterns)}")
172
+
173
+ if metadata:
174
+ print(" Metadata:")
175
+ for key, value in metadata.items():
176
+ print(f" {key}: {value}")
177
+
178
+ if args.show_patterns:
179
+ print("\n Patterns:")
180
+ for pattern in patterns[: args.limit]:
181
+ print(f" - {pattern}")
182
+ if len(patterns) > args.limit:
183
+ print(f" ... and {len(patterns) - args.limit} more")
184
+
185
+ return 0
186
+
187
+ except FileNotFoundError:
188
+ print(f"✗ Cache entry not found: {args.hash}", file=sys.stderr)
189
+ return 1
190
+
191
+
192
+ def cmd_validate(args):
193
+ """Validate YAML rules file."""
194
+ print(f"Validating: {args.rules}")
195
+
196
+ try:
197
+ parser = ConfigParser()
198
+ rules = parser.parse_file(args.rules)
199
+
200
+ print("✓ Valid YAML configuration")
201
+ print(f" Rules: {len(rules)}")
202
+
203
+ for name, config in rules.items():
204
+ print(f" - {name}: severity={config.severity}, penalty={config.penalty}")
205
+
206
+ return 0
207
+
208
+ except Exception as e:
209
+ print(f"✗ Validation failed: {e}", file=sys.stderr)
210
+ return 1
211
+
212
+
213
+ def main():
214
+ """Main CLI entry point."""
215
+ parser = argparse.ArgumentParser(
216
+ description="ReskLogits Rule Generator CLI",
217
+ formatter_class=argparse.RawDescriptionHelpFormatter,
218
+ )
219
+
220
+ subparsers = parser.add_subparsers(dest="command", help="Command to execute")
221
+
222
+ # Generate command
223
+ gen_parser = subparsers.add_parser("generate", help="Generate patterns from rules")
224
+ gen_parser.add_argument("rules", help="Path to YAML rules file")
225
+ gen_parser.add_argument("-o", "--output", help="Output file (JSON)")
226
+ gen_parser.add_argument("--no-synonyms", action="store_true", help="Disable synonym expansion")
227
+ gen_parser.add_argument("--no-cache", action="store_true", help="Disable caching")
228
+ gen_parser.add_argument("--force", action="store_true", help="Force regeneration")
229
+ gen_parser.add_argument(
230
+ "--by-category", action="store_true", help="Group output by rule category"
231
+ )
232
+ gen_parser.add_argument("--preview", type=int, default=20, help="Number of patterns to preview")
233
+ gen_parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
234
+
235
+ # Test command
236
+ test_parser = subparsers.add_parser("test", help="Test rule matching")
237
+ test_parser.add_argument("rules", help="Path to YAML rules file")
238
+ test_parser.add_argument("--text", required=True, help="Text to test against")
239
+ test_parser.add_argument("--no-cache", action="store_true", help="Disable caching")
240
+
241
+ # Expand command
242
+ expand_parser = subparsers.add_parser("expand", help="Expand and preview a specific rule")
243
+ expand_parser.add_argument("rules", help="Path to YAML rules file")
244
+ expand_parser.add_argument("--rule", required=True, help="Rule name to expand")
245
+ expand_parser.add_argument(
246
+ "--no-synonyms", action="store_true", help="Disable synonym expansion"
247
+ )
248
+ expand_parser.add_argument("--preview", type=int, default=50, help="Number of patterns to show")
249
+
250
+ # Cache subcommands
251
+ cache_parser = subparsers.add_parser("cache", help="Cache management")
252
+ cache_subparsers = cache_parser.add_subparsers(dest="cache_command")
253
+
254
+ cache_status_parser = cache_subparsers.add_parser("status", help="Show cache status")
255
+ cache_status_parser.add_argument(
256
+ "--cache-dir", default=".resklogits_cache", help="Cache directory"
257
+ )
258
+ cache_status_parser.add_argument(
259
+ "-v", "--verbose", action="store_true", help="Show detailed info"
260
+ )
261
+
262
+ cache_clear_parser = cache_subparsers.add_parser("clear", help="Clear cache")
263
+ cache_clear_parser.add_argument(
264
+ "--cache-dir", default=".resklogits_cache", help="Cache directory"
265
+ )
266
+ cache_clear_parser.add_argument("--hash", help="Clear specific entry")
267
+ cache_clear_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
268
+
269
+ cache_show_parser = cache_subparsers.add_parser("show", help="Show cache entry")
270
+ cache_show_parser.add_argument("hash", help="Cache entry hash")
271
+ cache_show_parser.add_argument(
272
+ "--cache-dir", default=".resklogits_cache", help="Cache directory"
273
+ )
274
+ cache_show_parser.add_argument("--show-patterns", action="store_true", help="Show patterns")
275
+ cache_show_parser.add_argument("--limit", type=int, default=20, help="Pattern limit")
276
+
277
+ # Validate command
278
+ validate_parser = subparsers.add_parser("validate", help="Validate YAML rules file")
279
+ validate_parser.add_argument("rules", help="Path to YAML rules file")
280
+
281
+ args = parser.parse_args()
282
+
283
+ if not args.command:
284
+ parser.print_help()
285
+ return 1
286
+
287
+ # Execute command
288
+ if args.command == "generate":
289
+ return cmd_generate(args)
290
+ elif args.command == "test":
291
+ return cmd_test(args)
292
+ elif args.command == "expand":
293
+ return cmd_expand(args)
294
+ elif args.command == "cache":
295
+ if args.cache_command == "status":
296
+ return cmd_cache_status(args)
297
+ elif args.cache_command == "clear":
298
+ return cmd_cache_clear(args)
299
+ elif args.cache_command == "show":
300
+ return cmd_cache_show(args)
301
+ else:
302
+ cache_parser.print_help()
303
+ return 1
304
+ elif args.command == "validate":
305
+ return cmd_validate(args)
306
+
307
+ return 1
308
+
309
+
310
+ if __name__ == "__main__":
311
+ sys.exit(main())